相向双指针
- 1、两数之和
-
- [1.1 题目描述](#1.1 题目描述)
- [1.2 解答思路](#1.2 解答思路)
- 2、三数之和
-
- [2.1 题目描述](#2.1 题目描述)
- [2.2 解答思路](#2.2 解答思路)
1、两数之和
1.1 题目描述

1.2 解答思路
如果我们使用暴力解法,即嵌套两个 for 循环从而寻找相加之和为目标的两个数,那么这个实现的时间复杂度是 O( n 2 n^2 n2),显然是需要优化的。
根据题目描述,我们可以看到还有个条件没有利用到,即 "该数组已按非递减顺序 排列" ,那么我们尝试利用有序数组对算法进行优化。
假设输入的数组是[2, 7, 11, 15],target = 9,如果我们知道 2+11 > 9,那么根据数组是非递减顺序的性质可知,2之后的数字加上11肯定更加大于目标值,那么此时就需要使得11更小,既然知道这个规律,我们就直接设置两个指针分别指向最左端和最右端,即最小值和最大值,此后如果两个指针指向的数字比目标值大,那么移动右指针使得两数之和更小,否则移动左指针使得两数之和更大。
python
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
# 左指针
left = 0;
# 右指针
right = len(numbers)-1;
while left<right:
# 如果两数之和大于目标值,那么需要大数变小一点,即右指针左移
if numbers[left] + numbers[right] > target:
right-=1;
# 如果两数之和小于目标值,那么需要小数变大一点,即左指针右移
elif numbers[left] + numbers[right] < target:
left+=1;
else:
return [left+1, right+1]
可知,上述算法的时间复杂度是 O(n),其中 n 是数组的长度;空间复杂度是 O(1)。
思路分析:
时间复杂度从 O( n 2 n^2 n2) 降为 O(n),主要原因是暴力解法在比较一次后只是获得了O(1) 的信息量,还需要比较 n 2 n^2 n2 次,但是相向双指针的解法在比较一次之后,可以直接舍去一个值,(由于左指针是从最小值开始的,右指针是从最大值开始的,当左指针所指的数和右指针所指的数加起来大于目标值,那么剩下的数和右指针所指的数加起来都会大于目标值,因此可以直接舍弃右指针所指的数)也就是获得了 O(n) 的信息量。
2、三数之和
2.1 题目描述

2.2 解答思路
由刚刚的两数之和可知,如果我们想要利用相向双指针的话,数组就得是有序的,而题目中已经说明输出的顺序和三元组的顺序并不重要,因此我们可以首先对数组进行排序。既然这里是三数之和等于0,那么我们可以将其转换为两数之和等于剩下的数的负数,即利用一个 for 循环就可以转换成我们熟悉的题型啦。
python
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
# 排序使得数组是有序的
nums.sort()
ans = []
length = len(nums)
# 利用一个 for 循环,将-nums[i]看作是两数之和的目标值
for i in range(length-2):
# 跳过重复值
if i>0 and nums[i]==nums[i-1]:
continue
# 优化一
if nums[i]+nums[i+1]+nums[i+2] > 0:
break
# 优化二
if nums[i]+nums[-1]+nums[-2] < 0:
continue
left = i+1
right = length-1
while left < right:
s = nums[left] + nums[right]
if s == -nums[i]:
ans.append([nums[i], nums[left], nums[right]])
left += 1
right -= 1
# 跳过左指针和右指针所指的数的重复值
while left < right and nums[left]==nums[left-1]:
left += 1
while left < right and nums[right]==nums[right+1]:
right -= 1
elif s < -nums[i]:
left += 1
else:
right -= 1
return ans
可知,上述算法的时间复杂度是 O( n 2 n^2 n2),其中 n 是数组的长度;空间复杂度是 O(1),这里忽略了排序的空间复杂度。
思路分析:
- 首先将数组排序,从而可以利用相向双指针的思路进行求解。
- 双指针只能求解两数之和,那么就可以将 nums[i]+nums[j]+nums[k] = 0 转换为 nums[i]+nums[j] = -nums[k] 即可。
- 这里有个容易出错的地方就是答案中不可以包含重复的三元组,因此我们在外层 for 循环遍历目标值的时候需要跳过重复值,当条件成立,即三数之和等于0的时候,不能再出现重复的答案,因此左右指针在移动的时候也要跳过重复的值。
- 利用数组非递减的性质,可以有两个优化:如果遍历剩下的数组中的前三个数,即当前最小的三个数加起来都大于0的话,那么剩下的数组中任意三个数加起来都会大于0,因此直接跳出循环,返回答案;如果遍历当前的 nums[i] 为目标值,nums[i] 和剩下的数组中的后两个数,即当前最大的两个数加起来都小于0的话,就不用再移动指针了,因为nums[i]和其他任意的两个数相加的和都会小于0,因此可以跳过当前循环,进行下个循环。