题目描述
找出数组里所有不重复的三元组,使三个数之和为 0。
思路解析
排序后固定 i,问题就变成在 i 右边找两个数,让它们的和等于 −nums[i]。l 和 r 往中间夹:和大了 r 左移,和小了 l 右移,正好命中就记下来。
第一步:排序:先从小到大排个序。有序了才能用对撞双指针。
固定 i=0 (−2):固定 −2,要在右边凑出和为 2 的两个数。当前三数和 = −1 小于 0,想让和变大,就把 l 右移(换个更大的数)。
i=0 · l 右移:l 移到下标 2(值 0),三数和 = 1 大于 0 了。这次反过来,把 r 左移(换个更小的数)。
i=0 · r 左移:r 移到下标 3(值 2),三数和 = −2 + 0 + 2 = 0,命中!记录 [−2, 0, 2]。
i=0 · 命中后双移:命中后 l 右移、r 左移 同时进行(并跳过重复值),继续找。此时 l、r 交叉,固定第一个 −2 这轮结束。
i=1 · 又是 −2 · 跳过:轮到固定下标 1,可它也是 −2,和上一个固定值一样。再做一遍只会得到重复的 [−2,0,2],所以直接跳过——这就是「去重」。
固定 i=2 (0):固定 0:最小的两数之和都已经大于 0,r 左移后很快交叉,没有新解。
结束:所有固定点试完,得到唯一三元组 [−2, 0, 2]。这一趟把小了右移、大了左移、命中、去重四种情况都演示了一遍。
排序后用左右指针把两数之和「夹」出来——盛水容器、两数之和 II 都是这套对撞手感。
参考代码(Python)
1nums.sort() # 必须先排序
2for i in range(len(nums)):
3 if nums[i] > 0: break
4 if i > 0 and nums[i]==nums[i-1]: continue # 去重
5 l, r = i + 1, len(nums) - 1
6 while l < r:
7 s = nums[i] + nums[l] + nums[r]
8 if s == 0: 记录; l+=1; r-=1; 跳过重复
9 elif s < 0: l += 1
10 else: r -= 1复杂度分析
- 时间复杂度:O(n²) —— 排序 + 双层 n×n
- 空间复杂度:O(1) —— 不算排序与答案
套路模板
记住:和小了挪左、和大了挪右。三数之和就是在它外面套一层「固定 i」。共 16 条。
1# 有序数组里找一对 / 逼近某个和
2l, r = 0, len(a) - 1
3while l < r:
4 s = a[l] + a[r]
5 if s == target: ...; l += 1; r -= 1
6 elif s < target: l += 1 # 要更大
7 else: r -= 1 # 要更小易错点
- ❌ 不排序就上双指针 → ✅ 必须先 nums.sort()(对撞的前提是有序)
- ❌ 忘了去重 → ✅ 固定点和移动后都跳过相同值(否则出现重复三元组)
- ❌ 命中后只动一个指针 → ✅ 命中要 l+=1 且 r-=1(否则原地重复命中)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。