
这道题可以说是昨天Leetcode11 2Sum的变体,本质上的思想还是用到了two pointers,但是需要注意的是,这道题的特殊情况是不能有重复,所以我们需要加一下限制条件。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res=[]
nums.sort()
for i in range(len(nums)):
if nums[i] > 0:
break
if i == 0 or nums[i - 1] != nums[i]:
self.twosum(nums,i,res)
return res
def twosum(self,nums:List[int],i: int,res:List[List[int]]):
l , r = i + 1,len(nums) - 1
while l < r :
sum = nums[i] + nums[l] + nums[r]
if sum < 0 :
l += 1
elif sum > 0 :
r -= 1
else:
res.append([nums[i],nums[l],nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l - 1]:
l += 1
在这个思路中,我们先进行排序,排序的目的是便于使用双指针技术,同时也可以在过程中快速跳过重复的元素,避免重复解。
然后我们使用 for 循环依次固定一个数 nums[i] 作为第一个数。如果 nums[i] > 0,可以直接结束循环,因为排序后数组的元素逐渐增大,无法再找到满足 a + b + c = 0 的组合(正数相加不会等于 0)。跳过重复值:if i == 0 or nums[i - 1] != nums[i] 用于确保不会重复使用相同的起点数。
然后我们调用twosum函数寻找之后的两个数,sum小于0的情况说明l需要右移,大于0则说明r需要左移,同样的我们在这里也要判断是否有重复的元素。
在做完这道题以后,如果我们需要一个4Sum又应该怎么操作呢

在上面的3Sum 我们看到 实际上可以把问题转化为2Sum来解决,那么针对4Sum,我们可以先变成3Sum,再变成2Sum,这就是递归的思想了,所以很快 ,我就有了这样一个思路。
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
results = []
self.findsum(nums,target,4,[],results)
return results
def findsum(self,nums:List[int],target: int,N:int,res:List[int],results:List[List[int]]):
if len(nums) < 2 or N < 2:
return
if N == 2:
l,r = 0, len(nums) -1
while l < r :
if nums[l] + nums[r] == target :
results.append(res + [nums[l] , nums[r]])
l += 1
r -= 1
while l < r and nums[l] == nums[l-1]:
l += 1
elif nums[l] + nums[r] < target:
l += 1
else:
r -= 1
else:
for i in range(0,len(nums)-N+1):
if target < nums[i] * N or target > nums[-1] * N:
break
if i == 0 or i > 0 and nums[i-1] != nums[i]:
self.findsum(nums[i+1:],target-nums[i],N - 1,res + [nums[i]],results)
return
那么我们首先要做的就是排序,然后建立一个可以递归的函数,N == 2 是我们的basecase,然后就是一些边界条件的考量。在写代码的过程中我还发现了一个很有趣的点,那就是r pointer的去重,我在写的过程中发现这行代码实际是不需要的
while l < r and nums[r] == nums[r+1]:
r -= 1
那么就有一个很有意思的点就是为什么l的去重是必须的,而r不是
l的去重逻辑:
while l < r and nums[l] == nums[l - 1]:
l += 1
1.l必须跳过重复值
作用: 在当前解法中,l向右移动是为了寻找新的数字组合。如果l没有跳过重复的值,那么在下一次计算中,会重新处理已经生成的组合,导致结果重复。
2. l从 l = i+1 开始,与右指针的范围独立
l是 N > 2 时递归向下传递的主要变量,因此对l的去重更直接影响最终的结果。
r去重的必要性较低
r的去重逻辑:
while l < r and nums[r] == nums[r + 1]:
r -= 1
1. 自然的r移动已避免重复
在two pointers中,找到符合条件的组合后,r会被直接左移(r -= 1),这通常已经足够避免r重复值被多次处理。r的重复值对结果的影响较低,因为大多数情况下它仅影响一次组合生成后的局部指针位置,而不影响最终结果集。
2. 如果保留l去重,r可以省略
如果l已经跳过了重复值,并且我们总是先处理l,再移动r,重复值问题已经通过l的去重逻辑解决。r的去重代码可以作为冗余逻辑移除。
两者有一个是否足够?
保留l,省略r:推荐
l跳过重复值是必须的,因为它是寻找新组合的核心逻辑。如果r没有去重,依靠右指针的自然移动(r -= 1),也不会影响结果正确性。
省略l,保留r:可能出问题
如果l没有跳过重复值,那么从头到尾处理相同l值时会多次生成相同的组合。即使r跳过了重复值,仍然会导致最终结果中存在重复。
举例分析
输入:
nums = [-2, -1, -1, 0, 1, 2],target = 0,固定 nums[i] = -2
l去重保留:
- 初始左指针:
l = 1,nums[l] = -1 - 右指针:
r = 5,nums[r] = 2 - 找到组合
[-2, -1, 1]后:- 左指针跳过重复值,移动到
nums[l+1] = 0 - 确保下一次计算从不同值开始。
- 左指针跳过重复值,移动到
如果省略l去重:
- 初始左指针:
l = 1,nums[l] = -1 - 右指针:
r = 5,nums[r] = 2 - 找到组合
[-2, -1, 1]后:- 左指针不跳过重复值,会再次处理相同的
nums[l] = -1,导致重复结果。
- 左指针不跳过重复值,会再次处理相同的

留下评论