LeetCode题型归纳——数组双指针
目录
一、双指针算法基础
所谓双指针算法,就是指的是在遍历的过程中,不是普通的使用单个指针进行循环访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的。双指针法充分使用了数组有序这一特征,从而在某些情况下能够简化一些运算,降低时间复杂度.
二、双指针算法分类
包含:
- 相向双指针
- 同向双指针 - 快慢指针
- 同向双指针 - 滑动窗口
- 分离双指针
下面分别介绍:
2.1 相向双指针
是指在有序数组中,将指向最左侧的索引定义为左指针 (left),最右侧的定义为右指针 (right),然后从两头向中间进行数组遍历 。(前提:数组有序,若无序,先排序)
2.1.1 两数之和(1)
方法一:暴力枚举
枚举数组中的每一个数 x,寻找数组中是否存在 target - x。当我们使用遍历整个数组的方式寻找 target - x 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
for i in range(len(nums) - 1):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length - 1; i++){
for (int j = i + 1; j < nums.length; j++){
if (nums[i] + nums[j] == target){
return new int[] {
i, j};
}
}
}
return new int[0];
}
}
方法二:哈希表解法
创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,若不存在则将 x 插入到哈希表中,即可保证不会让 x 和自己匹配,若存在,则返回 。
例如:
初始状态:
第一步:哈希表中没有11,所以把2插入,对应的索引0作为其值,
第二步:哈希表中没有6,所以把7插入,对应的索引1作为其值,
第三步:哈希表中有2,所以返回2对应的索引0和11对应的索引2
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 创建一个哈希表
res = {
}
# 遍历
for i in range(len(nums)):
if target - nums[i] not in res:
res[nums[i]] = i
else:
return [res[target - nums[i]], i]
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> hashtable = new HashMap<>();
for(int i = 0; i < nums.length; i++){
if(hashtable.containsKey(target - nums[i])){
return new int[] {
hashtable.get(target - nums[i]), i};
}else{
hashtable.put(nums[i], i);
}
}
return new int[0];
}
}
方法三:相向双指针解法
双指针(L,R)法的思路很简单, L指针用来指向第一个值, R指针用来从第L指针的后面查找数组中是否含有和L指针指向值和为目标值的数。
两个指针分别从左从右开始扫描,每次判断这两个数相加是不是等于target,如果小了,那就把左边的指针向右移,同理如果大了,就把右指针往左移。(前提:数组是有序的)
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 复制原数组,为后续找原数组中的位置做准备
array = nums.copy()
# 首先对数组进行从小到大排序
nums.sort()
# 初始化左右指针
left, right = 0, len(nums) - 1
# 扫描,找到满足和为target的两个数的位置
while left < right:
if nums[left] + nums[right] < target:
left += 1
elif nums[left] + nums[right] > target:
right -= 1
else:
break
res = []
# 找出该位置对应原数组中的索引
for i in range(len(array)):
if array[i] == nums[left] or array[i] == nums[right]:
res.append(i)
return res
修改一:
如果假设输入一个数组 nums 和一个目标和 target, 请你返回 nums 中能够凑出 target 的两个元素的值,比如输入 nums = [5,3,1,6], target = 9,那么算法返回两个元素 [3,6]。可以假设只有且仅有一对元素可以凑出 target。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 对数组进行排序
nums.sort()
# 初始化双指针
left, right = 0, len(nums) - 1
# 开始遍历
while left < right:
sums = nums[left] + nums[right]
if sums < target:
left += 1
elif sums > target:
right -= 1
else:
# 本修改需要返回的是两个元素的值
return [nums[left], nums[right]]
class Solution {
public int[] twoSum(int[] nums, int target) {
//方法三:排序+双指针
int[] temp = Arrays.copyOf(nums, nums.length);
Arrays.sort(nums); //升序排序
int begin = 0;
int end = nums.length - 1;
while(begin < end){
if(nums[begin] + nums[end] == target){
break;
}else if(nums[begin] + nums[end] > target){
end--;
}else{
begin++;
}
}
int a = -1;
int b = -1;
for(int i=0; i<temp.length; i++){
if(temp[i] == nums[begin] && a == -1){
a = i;
continue;
}
if(temp[i] == nums[end]){
b = i;
}
}
return new int[]{
a, b};
}
}
修改二:
nums 中可能有多对元素之和都等于 target,请你的算法返回所有和为 target 的元素对,其中不能出现重复,比如说输入为 nums = [1,3,1,2,2,3], target = 4,那么算法返回的结果就是:[[1,3],[2,2]]。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
# 先进行排序
nums.sort()
# 初始化双指针
left, right = 0, len(nums) - 1
res = []
# 开始遍历
while left < right:
sums = nums[left] + nums[right]
if sums < target:
left += 1
elif sums > terget:
right -= 1
else:
res.append([nums[left], nums[right]])
# 去重,注意数组是有序的,所以可以这样
while left < right and nums[left] == nums[left + 1]:
left += 1
while left < right and nums[right] == nums[right - 1]:
right -= 1
left += 1
right -= 1
return res
2.1.2 两数之和II - 输入有序数组(167)
具体步骤如下:
借助数组的有序性:
- 使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
- 如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
# 初始化双指针
left, right = 0, len(numbers) - 1
# 开始遍历
while left < right:
sums = numbers[left] + numbers[right]
if sums < target:
left += 1
elif sums > target:
right -= 1
else:
return [left + 1, right + 1]
# 若没有找到满足的元素对,返回[-1, -1]
return [-1, -1]
class Solution {
public int[] twoSum(int[] numbers, int target) {
int left = 0;
int right = numbers.length - 1;
while(left < right){
int s = numbers[left] + numbers[right];
if(s == target){
return new int[] {
left+1, right+1};
}else if(s > target){
right--;
}else{
left