15. 三数之和(中等)
题目
给定一个整数数组 nums,找出所有这样的三元组 [nums[i], nums[j], nums[k]]:下标 i、j、k 两两不同,并且 nums[i] + nums[j] + nums[k] == 0。
返回值中不能出现重复的三元组。这里「重复」只看三个数的值组合,不看下标:例如 [-1,0,1] 和 [0,-1,1] 算同一个。
自造示例:nums = [-3,0,1,2,0,0] → [[-3,1,2],[0,0,0]]。因为 -3+1+2=0,同时三个 0 也满足 0+0+0=0;其余任意三数之和都不为 0。
无解示例:nums = [0,1,2] → [],任何三个数加起来都不是 0。
约束:数组长度 3 ~ 3000;元素范围 -105 ~ 105。
思路
一句话:固定一个数,把「三数之和」降维成「有序数组中的两数之和」,再用双指针一次线性扫完。
最直白的做法是三重循环枚举 i、j、k,找到和为 0 的存下来,最后去重。它有两个问题:一是 O(n³),当 n=3000 时约 2.7×1010 次,跑不动;二是去重要靠集合保存「已经出现过的三元组」,麻烦且占空间。
稍微改进:枚举一个 i 之后,剩下的问题是找 nums[j] + nums[k] == -nums[i]。但若数组无序,找两数之和还得靠哈希,而三元组整体去重依旧绕不开。
关键观察是先排序。排序带来两件好事:
- 去重变简单:相等的数会挤在一起,跳过相邻的相等值即可,不需要额外集合。
- 双指针可行:对升序数组,左指针
j右移只会让和变大,右指针k左移只会让和变小。于是和偏小就j++、偏大就k--,两个指针一路向中间靠拢,每次内层至多走 O(n) 步。
最终复杂度:排序 O(n log n),加枚举 i 的 O(n) 次内层双指针 O(n),整体 O(n²)。
解法
- 把
nums升序排序。 - 枚举第一个数
i(到n-3即可,要给j、k留两个位置)。 - 如果
nums[i] > 0,直接结束:排序后最小的数都为正,后面不可能凑出 0。 - 如果
i > 0且nums[i] == nums[i-1],跳过,避免第一个数重复。 - 令
j = i+1、k = n-1,在j < k内双指针:- 和小于 0 →
j++(要更大的数); - 和大于 0 →
k--(要更小的数); - 和等于 0 → 记录
[nums[i], nums[j], nums[k]],然后同时跳过j侧和k侧的重复值,再各自向中间收一步。
- 和小于 0 →
- 返回所有记录。
以排序后的 [-3,0,0,0,1,2] 为例走一遍:
固定 i | 左指针 j | 右指针 k | 三数之和 | 动作 |
|---|---|---|---|---|
| -3 | 0(下标1) | 2(下标5) | -1 | 小于 0,j++ |
| -3 | 0(下标2) | 2(下标5) | -1 | 小于 0,j++ |
| -3 | 0(下标3) | 2(下标5) | -1 | 小于 0,j++ |
| -3 | 1(下标4) | 2(下标5) | 0 | 命中 [-3,1,2],收拢双指针 |
| 0(下标1) | 0(下标2) | 2(下标5) | 2 | 大于 0,k-- |
| 0(下标1) | 0(下标2) | 1(下标4) | 1 | 大于 0,k-- |
| 0(下标1) | 0(下标2) | 0(下标3) | 0 | 命中 [0,0,0],收拢双指针 |
接着 i 移到下标 2、3(值都是 0),与上一个值重复,被跳过;下标 4 的值是 1,已经大于 0,直接结束。最终得到 [[-3,1,2],[0,0,0]]。
下图把上面 8 个步骤串成流程,并逐步演示:


C++
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> res;
sort(nums.begin(), nums.end()); // 排序:方便双指针 + 去重 for (int i = 0; i < n - 2; ++i) {
if (nums[i] > 0) break; // 最小数都为正,后面不可能凑 0
if (i > 0 && nums[i] == nums[i - 1]) continue; // 第一个数去重 int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
if (sum < 0) {
++j;
} else if (sum > 0) {
--k;
} else {
res.push_back({nums[i], nums[j], nums[k]});
while (j < k本篇为付费内容
您已阅读试读部分,购买后即可解锁全文。
⚠️ 本站内容已做唯一性标记并受版权保护,未经授权禁止转载,侵权必究。
还没有评论,来说两句?