最近在力扣上刷题,一连刷到了好多n数之和的题,做完后把它们放在一起比较分析了一下,发现其实n数之和问题似乎都有这样的一个思想贯彻其中——“排序+双指针”,下面就让我们通过这些例题来一起感受解决这类问题的方法吧!
例题
1.两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。你可以按任意顺序返回答案。
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(0,len(nums)-1):
for j in range(i+1,len(nums)):
if nums[i] + nums[j] ==target:
return [i,j]
两数之和还是非常简单的,用两个循环遍历寻找就行了,双指针还没有用武之地。
2.三数之和
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
nums.sort()
ans = []
ln, rn = 0, n-1 # 左指针和右指针的初始位置分别为数组的首尾
for i in range(ln, rn-1): # 循环遍历数组中的每个数字
if i > 0 and nums[i] == nums[i-1]: # 跳过重复的数字
continue
target = -nums[i] # 将三数之和转化为两数之和的问题
ln = i + 1
rn = n - 1
while ln < rn:
if nums[ln] + nums[rn] == target: # 找到满足条件的三元组
ans.append([nums[i], nums[ln], nums[rn]])
while ln < rn and nums[ln+1] == nums[ln]: # 跳过重复的数字
ln += 1
while ln < rn and nums[rn-1] == nums[rn]: # 跳过重复的数字
rn -= 1
ln += 1
rn -= 1
elif nums[ln] + nums[rn] < target: # 两数之和小于目标值,将左指针右移一位
ln += 1
else: # 两数之和大于目标值,将右指针左移一位
rn -= 1
return ans
三数之和肉眼可见地复杂了很多,这时候就到了“排序+双指针”大法大显身手的时候啦!首先给整个数组排序,排序是为了可以通过比较大小来控制左右指针的移动。双指针一下子解决了两数,剩下的一个数字只要通过循环就可以解决,比较now和target的大小来控制左右指针的移动,最后得出结果。
3.最接近的三数和
给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。返回这三个数的和。(假定每组输入只存在恰好一个解)
class Solution:
def threeSumClosest(self, nums: List[int], target: int) -> int:
n=len(nums)
ans = inf
nums.sort()
for i in range(n-2):
ln,rn=i+1,n-1
while ln<rn:
may=nums[i]+nums[ln]+nums[rn]
goal=target-nums[i]
now=nums[ln]+nums[rn]
ans=ans if abs(target-ans)<abs(target-may)else may
if now==goal:
return target
if now<goal:
ln+=1
else:
rn-=1
return ans
可以看到最接近的三数和与三数之和异曲同工,无非是把等于转换成了距离(差的绝对值)的比较,其余都基本一致。
4.四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):0 <= a, b, c, d < n,a、b、c 和 d 互不相同,nums[a] + nums[b] + nums[c] + nums[d] == target(你可以按任意顺序返回答案 )
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
nums.sort()
n = len(nums)
for i in range(n):
# 确定第一个数
if i > 0 and nums[i - 1] == nums[i]:
continue # 找到下一个不一样的值,去重
# 确定第二个数
for j in range(i + 1, n):
if j > i + 1 and nums[j - 1] == nums[j]:
continue # 找到下一个不一样的值,去重
# 寻找剩下的两个数
left = j + 1
right = n - 1
two_number_target = target - nums[i] - nums[j]
while left < right:
two_number_sum = nums[left] + nums[right] # 两数之和
if two_number_sum == two_number_target:
res.append([nums[i], nums[j], 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 two_number_sum < two_number_target:
left += 1 # 两数之和小于两数目标值,left增大,增大加数
else:
right -= 1 # 两数之和大于两数目标值,right减小,减小加数
return res
可以看到四数之和更加复杂,但是万变不离其宗,还是祭出我们的“排序+双指针”大法,干就完了!
总结
其实可以发现,所谓的“排序+双指针”大法其实就是通过引入双指针的方法来代表其中的两个数,这样避免了多层循环产生浪费,提高了效率。双指针确实是个非常实在且好用的方法,值得我们大家好好琢磨钻研,掌握并且熟练运用。