三数之和问题(3Sum)是算法面试中的经典题型,也是LeetCode热题100中的高频考点。给定一个包含n个整数的数组nums,判断其中是否存在三个元素a、b、c,使得a + b + c = 0?需要找出所有满足条件且不重复的三元组。
这个问题的难点在于:
我在面试候选人和实际刷题过程中发现,90%的初学者会先尝试暴力枚举,然后陷入去重的泥潭。而双指针法正是解决这类问题的银弹——它能在O(n²)时间内高效解决问题,同时优雅地处理重复情况。
双指针法的本质是"降维"——将三数之和转化为两数之和问题。具体步骤:
去重是这个算法的精妙之处,体现在两个层面:
python复制if i > 0 and nums[i] == nums[i-1]:
continue
当当前数字与前一个相同时跳过,避免重复解如[-1,-1,2]和[-1,-1,2]
python复制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
找到解后,跳过所有相邻重复元素,确保每个解唯一
python复制def threeSum(nums):
nums.sort()
res = []
n = len(nums)
for i in range(n-2):
if nums[i] > 0: # 优化点1:首数大于0时提前终止
break
if i > 0 and nums[i] == nums[i-1]: # 外层去重
continue
left, right = i+1, n-1
while left < right:
total = nums[i] + nums[left] + nums[right]
if total < 0:
left += 1
elif total > 0:
right -= 1
else:
res.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 += 1
right -= 1
return res
排序预处理:
nums.sort():双指针法的基础,保证数组有序才能正确移动指针提前终止条件:
if nums[i] > 0: break:当固定数已经大于0时,后续不可能有三数之和为0指针移动逻辑:
相比暴力解法的O(n³),性能提升显著。实测当n=3000时:
python复制if len(nums) < 3:
return []
if len(nums) == 3:
return [nums] if sum(nums) == 0 else []
python复制if nums[i] + nums[i+1] + nums[i+2] > 0:
break # 最小三数和已大于0
if nums[i] + nums[-2] + nums[-1] < 0:
continue # 当前i的最大三数和仍小于0
python复制seen = set()
for i in range(n-2):
if nums[i] in seen:
continue
seen.add(nums[i])
# ...双指针逻辑...
全零数组:
正负数交替:
无解情况:
在开发过程中添加调试打印:
python复制print(f"i={i}, left={left}, right={right}")
print(f"Current triplet: {nums[i]}, {nums[left]}, {nums[right]}")
print(f"Total: {total}, Res: {res}")
典型调试场景:
LeetCode 16题是此问题的变种:找和最接近target的三元组。解法类似,但需要维护最小差值:
python复制min_diff = float('inf')
res = 0
# ...双指针循环中...
if abs(total - target) < min_diff:
min_diff = abs(total - target)
res = total
LeetCode 18题将问题扩展到四个数。解法思路:
这类算法在以下场景有实际应用:
java复制// 需要处理List和Array的转换
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
// 去重检查使用Integer.equals()而非==
cpp复制// 注意vector的排序和去重
sort(nums.begin(), nums.end());
// 移动指针时注意不要越界
while (left < right && nums[left] == nums[left+1]) ++left;
javascript复制// 注意比较时的类型转换
nums.sort((a,b) => a - b); // 必须传比较函数!
// 数组是引用类型,注意不要修改原数组
当被问到这个问题时,建议按以下结构回答: