
对撞指针经典进阶组合:三数之和、四数之和。属于面试必考题。两道题核心思路一致:排序 + 固定外层数字 + 双指针寻找剩余值 ,难点不在于双指针本身,而在于去重逻辑,很多人代码能跑,但重复结果处理不干净。
前言
两数之和(有序版)是对撞指针入门;三数、四数之和,就是把「多枚举一层 + 双指针」结合。 暴力做法:三数 \(O(n^3)\),四数 \(O(n^4)\),大数据直接超时。 优化思路:先排序,固定前面 1/2 个数,剩下部分用左右对撞指针查找目标,把复杂度降下来。 两道题共同难点:跳过重复元素,避免输出重复的答案组合。
题目 1:三数之和15. 三数之和 - 力扣(LeetCode)

题目描述
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k,同时还满足 nums[i] + nums[j] + nums[k] = 0。 请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
cpp
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
思路分析
- 数组先升序排序,方便双指针移动 + 去重。
- 外层循环固定第一个数
nums[i]- 如果
nums[i]>0:后面所有数字都≥nums i,三数之和必然大于 0,直接 break 结束循环。 - 去重 :如果当前
nums[i]和上一个数字相等,直接 continue,跳过重复,防止产生一样的三元组。
- 如果
- 在 i 的右侧区间
[i+1, n-1]设置对撞指针:left = i + 1,right = n - 1- 求和
sum = nums[i] + nums[left] + nums[right]- sum == 0:找到一组解,加入结果集;左右指针都要去重,跳过相同值;left++, right--
- sum < 0:总和偏小,left++,增大总和
- sum > 0:总和偏大,right--,减小总和
核心坑点:三处去重(i 去重、left 去重、right 去重),很多新手只写一层去重,导致答案重复。
C++ 代码
cpp
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n; i++){
// 第一个数大于0,不可能凑出和为0
if(nums[i] > 0) break;
// 对i去重
if(i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = n - 1;
while(left < right){
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0){
res.push_back({nums[i], nums[left], nums[right]});
// left去重
while(left < right && nums[left] == nums[left+1]) left++;
// right去重
while(left < right && nums[right] == nums[right-1]) right--;
left++;
right--;
}else if(sum < 0){
left++;
}else{
right--;
}
}
}
return res;
}
复杂度分析
- 时间复杂度:排序 \(O(n\log n)\);外层循环\(O(n)\),内层双指针\(O(n)\);整体 \(O(n^2)\)
- 空间复杂度:\(O(\log n)\),排序栈开销(不计结果数组)
题目 2:四数之和18. 四数之和 - 力扣(LeetCode)


题目描述
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复 的四元组 [nums[a], nums[b], nums[c], nums[d]]:
- \(0 \le a < b < c < d < n\)
- \(numsa + numsb + numsc + numsd = target\)
注意:答案中不可以包含重复的四元组。
示例:
cpp
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
思路分析
三数之和是固定 1 层,四数之和固定两层,剩下依旧用左右对撞指针。
- 数组升序排序。
- 第一层循环固定
nums[i],i 去重 - 第二层循环固定
nums[j](j 起始为 i+1),j 去重 - 在区间
[j+1, n-1]设置对撞指针 left、right- sum = numsi+numsj+numsleft+numsright
- sum == target:记录答案,left、right 分别去重;left++, right--
- sum < target:left++
- sum > target:right--
重点:四数之和不能直接用三数之和那种
nums[i]>target剪枝!因为存在负数相加变小,剪枝条件要小心,容易剪错。
C++ 代码
cpp
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for(int i = 0; i < n; i++){
// i去重
if(i > 0 && nums[i] == nums[i-1]) continue;
for(int j = i + 1; j < n; j++){
// j去重
if(j > i + 1 && nums[j] == nums[j-1]) continue;
int left = j + 1;
int right = n - 1;
while(left < right){
// 注意long long防止int溢出
long long sum = (long long)nums[i] + nums[j] + nums[left] + nums[right];
if(sum == target){
res.push_back({nums[i], nums[j], nums[left], nums[right]});
// left去重
while(left < right && nums[left] == nums[left+1]) left++;
// right去重
while(left < right && nums[right] == nums[right-1]) right--;
left++;
right--;
}else if(sum < target){
left++;
}else{
right--;
}
}
}
}
return res;
}
⚠️ 重要细节:四个 int 相加可能溢出,强制转 long long,笔试高频坑!
复杂度分析
- 时间复杂度:排序 \(O(n\log n)\);两层循环 \(O(n^2)\),内层双指针\(O(n)\);整体 \(O(n^3)\)
- 空间复杂度:\(O(\log n)\),排序栈开销(不计结果数组)
📌 两道题对比总结(博客重点)
表格
| 题目 | 固定层数 | 指针模型 | 核心难点 | 溢出风险 |
|---|---|---|---|---|
| 三数之和 | 固定 1 层 (i) | left、right 对撞 | i/left/right 三处去重 | 较小,一般不会溢出 |
| 四数之和 | 固定 2 层 (i,j) | left、right 对撞 | i/j/left/right 四处去重;剪枝陷阱 | 极易 int 溢出,必须 long long |
共同点:
- 先排序,再固定外层元素,剩余区间使用对撞指针;
- 去重逻辑是两道题最大考点;
- 本质是暴力枚举的优化,把高次复杂度降一档。
通用扩展思路:k 数之和 k=2:有序数组直接双指针; k>2:固定 k-2 层循环,最后两个数用对撞指针;k 越大,复杂度越高。
小结
三数之和是对撞指针的分水岭,很多人能看懂双指针移动,但写不对去重。四数之和就是三数之和的套娃升级,多一层循环、多一处去重,额外增加整数溢出陷阱。 掌握这两道,你就掌握了「k 数之和」的通用解题框架。面试时如果被问到 k 数之和,可以直接把这套思路讲出来,加分项。

