题目描述
15.三数之和
链接:https://leetcode-cn.com/problems/3sum/
给你一个包含 n
个整数的数组 nums
,判断 nums
中是否存在三个元素 a
,b
,c
,使得 a + b + c = 0
?请你找出所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
18.四数之和
链接:https://leetcode-cn.com/problems/4sum/
给定一个包含 n
个整数的数组 nums
和一个目标值 target
,判断 nums
中是否存在四个元素 a
,b
,c
和 d
,使得 a + b + c + d
的值与 target
相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
其实这两道题前面还有一道更加基础的题,那就是第一题,两数之和。
因为题目比较简单,而且我觉得大部分人也应该都知道这道题,所以这里就不再贴第一题了。
不过这几道题都很经典,面试的时候会被经常问道。
这几道题也都不难,但是有一定的技巧在里面。掌握了不仅有助于拓展思维,而且对于面试求职也有很大帮助。
题目分析
做这类题的话,如果之前没有见过的话,很大可能就只能选择暴力求解了。不是说这样做不行,实在想不到其他方法了,也可以。
但是这类题目还有一种解法,叫做双指针,说实话,我刚开始做的时候也是完全不懂,但是自己做完后查看别人写的优秀代码后,发现双指针不仅代码优雅,而且非常高效。
就拿三数之和来说,如果按照暴力来进行求解的话,需要设置三层循环,但是双指针解法可以减少一层循环。
具体的代码如下:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
ans = []
if len(nums) < 3:
return ans
nums.sort()
for i in range(len(nums)-2):
# 如果最小的三个数已经大于 0,退出程序
if nums[i] + nums[i+1] + nums[i+2] > 0:
break
# 如果最大的三个数还小于0,continue
if nums[i] + nums[-1] + nums[-2] < 0:
continue
# 如果当前这个数等于前一个数, continue
if i > 0 and nums[i] == nums[i-1]:
continue
# 双指针
left, right = i + 1, len(nums) - 1
while left < right:
three_sum = nums[i] + nums[left] + nums[right]
# 如果三数之和等于 0,
if three_sum == 0:
ans.append([nums[i], 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, right的值
left += 1
right -= 1
# 如果三数之和小于 0
elif three_sum < 0:
left += 1
# 如果三数之和大于 0
else:
right -= 1
return ans
复制代码
看懂了三数之和,四数之和就和三数之和一样了。
代码如下:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
ans = []
if len(nums) < 4:
return ans;
nums.sort()
length = len(nums)
for i in range(length):
if nums[i] > 0 and target < 0:
break
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i+1, length):
if j > i+1 and nums[j] == nums[j-1]:
continue
two_sum = nums[i] + nums[j]
if two_sum > 0 and target < 0:
break
left, right = j + 1, length - 1
while left < right:
four_sum = two_sum + nums[left] + nums[right]
# residual = target - nums[i] - nums[j] - nums[left] - nums[right]
if four_sum == target:
ans.append([nums[i], nums[j], 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
elif four_sum < target:
left += 1
else:
right -= 1
return ans
复制代码
延伸
有了三数之和、四数之和,那么接下来还有五数之和,以及 N 数之和...,那么有没有一种通用的方法呢?
当前的方法也可以,不过需要事先确定 N,如果 N 不确定的话,就没办法了。
这个时候递归就派上用场了,而且同样可以使用双指针。
具体实现看代码:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
def nSum(nums: List[int], n: int, target: int) -> List[List[int]]:
res = []
if len(nums) < n:
return res
if n == 2:
left, right = 0, len(nums) - 1
while left < right:
if nums[left] + nums[right] == target:
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
elif nums[left] + nums[right] < target:
left += 1
else:
right -= 1
return res
else:
for i in range(len(nums)-n+1):
if i > 0 and nums[i] == nums[i-1]:
continue
min_sum = sum(nums[i:i+n])
if min_sum > target:
break
max_sum = nums[i] + sum(nums[-n+1:])
if max_sum < target:
continue
sub_res = nSum(nums[i+1:], n-1, target-nums[i])
for j in range(len(sub_res)):
res.append([nums[i]]+sub_res[j])
return res
nums.sort()
res = nSum(nums, 3, 0)
return res
复制代码
对于四数之和的话,将对应的参数修改下,就可以了。
总结
双指针是比较经典的一种方法,使用得当的话不仅可以写出优雅的代码,而且效率也很高。
如果觉得自己已经理解了的话,可以去 LeetCode 上实际写下。看看自己到底有没有掌握。
LeetCode 上更多的关于双指针的题目链接:
https://leetcode-cn.com/tag/two-pointers/
如果有其他问题的话,欢迎评论交流。
另外,欢迎关注我的微信公 &&众 &&号 「与你一起学算法」,领取刷题福利。
评论