LC15 三数之和¶
中等通过率 40.6%python3核心代码模式双指针样例通过牛客 AC数组双指针排序
讲解章节:双指针与滑动窗口
一句话
排序后固定第一个数,剩下两个用相向双指针在有序区间里对撞。
解题思路¶
这题考什么¶
要找出所有和为 0 且互不重复的三元组。三重循环是 O(n^3), n = 3000 时 2.7e10 次,不可能。
先排序,问题立刻变形:固定最小的那个数 nums[i] 之后, 剩下的任务是「在有序数组 nums[i+1 .. n-1] 里找一对和为 -nums[i] 的数」。 有序是关键——相向双指针 l、r 可以用 O(n) 解决它:
s = nums[l] + nums[r]
s < 目标 -> 和太小,只能把 l 右移换更大的数(r 左移只会更小)
s > 目标 -> 和太大,只能把 r 左移
s == 目标 -> 记下答案,l 右移且 r 左移,继续找别的组合
每次比较都能确定性地淘汰一个端点,所以内层一趟就够。 见 双指针与滑动窗口。
排序还顺手解决了去重:相同的值在排序后必然相邻, 于是「跳过重复」变成「跳过与前一个相同的元素」这种局部判断, 不需要拿 set 存已经产出的三元组(那要对每个三元组做一次哈希, 还得把 list 转成 tuple,常数大得多)。
去重要在三个位置各做一次:
- 外层 i:nums[i] == nums[i-1] 时,以 nums[i] 打头能配出的三元组, 上一轮已经全部找过了(候选区间只是少了一个元素,而那个元素等于 nums[i], 真要用到它,上一轮的 l 也能取到)。
- 内层命中后的 l 与 r:把与刚用过的值相同的位置整段跳过。
数据规模与复杂度¶
3 <= n <= 3000,-1e5 <= nums[i] <= 1e5。 排序 O(n log n),外层 n 轮、每轮内层 O(n),合计 O(n^2), 约等于 9e6 次基本操作。n = 3000 的随机数据本地实测 0.25 秒,判题机比本机慢数倍, 仍有余量。O(n^3) 的三重循环是 2.7e10,差三个数量级。 空间除答案外 O(1)(Python 的 sort 是原地的 Timsort)。
坑在哪¶
- 三处去重缺一不可,而且外层去重必须是
i > 0 and nums[i] == nums[i-1], 不能写成nums[i] == nums[i+1] 就跳过。后者会把 [0, 0, 0] 这种 必须用到重复值的答案整个跳掉——重复值本身是允许出现在同一个三元组里的, 不允许的只是同一个三元组被输出两次。 - 命中之后 l 与 r 必须同时移动。只动一边的话,另一边不变、和必然不再等于目标, 虽然不会错但白跑一轮;更糟的是若只动一边又忘了跳重复值, 会把同一个三元组反复塞进答案。
- 跳重复的 while 里要带边界条件
l < r,否则 [0, 0, 0, 0] 这类数据会让 l 冲过 r 越界。 - 提前剪枝
nums[i] > 0 就 break是安全的:数组已排序,nums[i] 是三个数里 最小的那个,它为正则三数之和必然为正,后面的 i 更不可能凑出 0。 这一条把大量正数的数据从 O(n^2) 压到几乎线性。 - 判题按
unordered_deep比对,因为题面原文「输出的顺序和三元组的顺序 并不重要」——外层三元组之间的先后、以及每个三元组内部三个数的先后都放宽。 顺序放宽不等于内容放宽:同一个三元组吐两次照样判错,所以去重不能省。 - 没有答案时返回空列表。题面示例 2 的 [0, 1, 1] 就是这种情况, ans 保持初值即可,不必特判。
样例复核¶
nums = [-1, 0, 1, 2, -1, -4],排序后 [-4, -1, -1, 0, 1, 2]。
i=0 nums[i]=-4 目标 4 l=1 r=5: -1+2=1 <4 -> l=2
l=2 r=5: -1+2=1 <4 -> l=3
l=3 r=5: 0+2=2 <4 -> l=4
l=4 r=5: 1+2=3 <4 -> l=5,l==r 退出
i=1 nums[i]=-1 目标 1 l=2 r=5: -1+2=1 命中 -> [-1,-1,2]
跳重复后 l=3 r=4: 0+1=1 命中 -> [-1,0,1]
l=4 r=3 退出
i=2 nums[i]=-1 与 nums[1] 相同 -> 跳过(否则会再产出一次 [-1,0,1])
i=3 nums[i]=0 目标 0 l=4 r=5: 1+2=3 >0 -> r=4,退出
i=4 nums[i]=1 > 0 -> break
得到 [[-1,-1,2], [-1,0,1]],与样例一致。
参考实现¶
[:octicons-arrow-left-16: LC11](LC11.md) [LC17 :octicons-arrow-right-16:](LC17.md)