🌿 从一数到二数、三数,再到四数,双指针思维的进阶之路至此完整。
🌱 文章摘要
这是《双指针专题》的最后一篇。
从“移动零”开始,我们经历了划分、复写、快慢、对撞、计数、查找与三数之和,逐步走完了双指针的全景路线。
而这一讲《四数之和》,将把所有技巧再向前推进一层:
- 固定两位,
- 双指针收缩查找 pair,
- 结合去重策略
高效地在 O(n³) 时间内找到所有和为 target 的四元组。
这是多数之和问题的“完全体”,也是滑动窗口专题开启前最有含金量的一篇。
🧭 导读
欢迎来到《双指针算法专题》第八篇,也是本系列的收官之作 🎉
《四数之和》的题目是“三数之和”的自然进阶:
-
给定一个数组和目标值 target,
-
找出所有不重复的四元组,使它们的和等于 target。
最直接的做法是四重循环 O(n⁴),但我们可以通过
- 排序
- 固定两位数 i、j
- 剩余区间用双指针查找 pair
将复杂度降为 O(n³),且逻辑十分清晰:
这道题的核心不是“技巧翻新”,而是把你在“三数之和”中学到的内容再 抽象一层,并做好去重细节的处理。
🧭 本系列目录
📚 建议收藏此目录,方便跳转阅读
【双指针专题】第六讲:查找总价格为目标值的两个商品-优快云博客
一、题目描述
给你一个包含 n 个整数的数组
nums和一个目标值target,请你找出所有 不重复的四元组[a, b, c, d],使得:a + b + c + d = target返回这些四元组列表。
注意:
- 四个下标必须互不相同;
- 答案中不能有重复的四元组;
- 结果顺序不重要。
示例:
输入: nums = [1, 0, -1, 0, -2, 2], target = 0 输出: [ [-2, -1, 1, 2], [-2, 0, 0, 2], [-1, 0, 0, 1] ]
二、思路引导:固定两位 + 双指针查找
如果用最直接的暴力法,四重循环枚举所有 a、b、c、d,时间复杂度 O(n⁴),显然会超时。
所以我们要换个角度:
-
三数之和可以通过「固定一位 + 双指针」来优化;
-
四数之和自然也可以「固定两位 + 双指针」来降低复杂度。
步骤如下:
-
排序 数组,为去重和双指针做准备;
-
固定 i → 第一位数字;
-
固定 j → 第二位数字;
-
在
[j+1, n-1]区间用 双指针 夹逼查找剩下两数; -
sum 小于目标 → 左指针右移;
sum 大于目标 → 右指针左移;
sum 恰好等于目标 → 加入答案,并跳过重复。
这个过程本质就是三数之和再多套一层循环,思路完全一致,只是去重和边界处理要更细。
三、算法流程
步骤 1:排序
对数组进行排序,便于去重和使用双指针。
步骤 2:外层循环固定 i
遍历 i ∈ [0, n-4],跳过重复的 nums[i]。
步骤 3:内层循环固定 j
遍历 j ∈ [i+1, n-3],跳过重复的 nums[j]。
步骤 4:双指针查找
设
left = j + 1,right = n - 1,目标为remain = target - nums[i] - nums[j]
若 sum < remain → left++
若 sum > remain → right--
若相等 → 加入答案,left++、right--,并跳过重复值。
步骤 5:遍历结束
返回所有找到的四元组。
四、代码实现(C++)
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
int n = nums.size();
if (n < 4) return res;
sort(nums.begin(), nums.end());
for (int i = 0; i < n - 3; i++) {
// 固定第一位,跳过重复
if (i > 0 && nums[i] == nums[i - 1]) continue;
for (int j = i + 1; j < n - 2; j++) {
// 固定第二位,跳过重复
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
int left = j + 1, right = n - 1;
long long remain = (long long)target - nums[i] - nums[j];
while (left < right) {
long long sum = (long long)nums[left] + nums[right];
if (sum < remain) left++;
else if (sum > remain) right--;
else {
res.push_back({nums[i], nums[j], nums[left], nums[right]});
left++; right--;
while (left < right && nums[left] == nums[left - 1]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
}
}
return res;
}
};
五、代码剖析与细节问题
1. 去重是关键
i、j 外层循环跳过相邻重复值,是为了避免生成相同开头的四元组。
内层双指针在找到一个四元组后,也必须跳过重复的 left / right,否则会出现重复解。
如果忘记这一步,几乎一定会出错,是这题最大的坑之一。
2. long long 防溢出
因为 target 与 nums[i]、nums[j] 可能都较大,计算 remain 和 sum 时用 long long 防止 int 溢出导致逻辑错误。
3. 循环边界要注意
- i 最大取到 n - 4
- j 最大取到 n - 3
- left = j + 1, right = n - 1
- 这些边界如果写错,很容易漏掉合法组合。
六、复杂度分析
- 时间复杂度:O(n³)
两重循环 O(n²) + 双指针 O(n),合计 O(n³)。
- 空间复杂度:O(1)
七、总结
“四数之和”几乎没有新的技巧,更多是对“三数之和”思路的 延展与细化。
这类题的本质就是:
「固定若干位 → 双指针 → 去重」,
再按层数递进处理。
这也是 K 数之和问题的通用模板。
🏁 收官寄语
至此,我们的“双指针”专题正式收官。从“移动零”到“四数之和”,你已经经历了
- 前向扫描
- 反向复写
- 快慢追击
- 区间对撞
- 计数统计
- 多数之和
这一整套逻辑思维的搭建,是很多人从“会写题”迈向“理解题”的分水岭。
接下来,我们将正式进入下一个专题——
👉 滑动窗口,
👉 聚焦子区间问题与动态维护,
👉 这是算法进阶的又一条主线 🚀


被折叠的 条评论
为什么被折叠?



