传送门:15. 三数之和 - 力扣(LeetCode)
🔍 题目要求回顾:
给定一个数组 nums
,找出所有满足条件的三元组 [a, b, c]
,使得:
a + b + c = 0
- 三元组不能重复(例如
[1, -1, 0]
和[0, 1, -1]
被视为同一个组合)
🧠 解题思路
这个问题的核心在于如何高效地找到所有满足条件的三元组,并且避免重复。
✅ 使用“排序 + 双指针”策略:
- 排序:先对数组进行排序,方便后续去重和双指针查找。
- 固定第一个数 a = nums[i],然后在剩下的部分中使用双指针找两个数 b 和 c,使得
a + b + c == 0
。 - 去重逻辑:
- 如果当前固定值
nums[i]
和前一个相同,跳过(避免重复的三元组)。 - 当找到一组满足条件的三元组后,继续移动左右指针,跳过重复值(防止重复加入)。
- 如果当前固定值
代码
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
// 存储最终结果的数组
let ans = [];
// 获取数组的长度
const len = nums.length;
// 如果数组为空或长度小于 3,直接返回空数组
if(nums == null || len < 3) return ans;
// 对数组进行升序排序,方便后续去重和双指针操作
nums.sort((a, b) => a - b);
// 遍历数组,固定第一个数
for (let i = 0; i < len ; i++) {
// 如果当前数字大于 0,由于数组已排序,后续数字也大于 0,三数之和一定大于 0,结束循环
if(nums[i] > 0) break;
// 去重:如果当前数字和前一个数字相同,跳过当前循环,避免结果重复
if(i > 0 && nums[i] == nums[i-1]) continue;
// 左指针,从固定数字的下一个位置开始
let L = i+1;
// 右指针,从数组末尾开始
let R = len-1;
// 双指针循环,寻找和为 0 的组合
while(L < R){
// 计算当前三个数的和
const sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
// 如果和为 0,将该组合添加到结果数组中
ans.push([nums[i],nums[L],nums[R]]);
// 左指针去重,跳过相同的数字
while (L<R && nums[L] == nums[L+1]) L++;
// 右指针去重,跳过相同的数字
while (L<R && nums[R] == nums[R-1]) R--;
// 移动左指针
L++;
// 移动右指针
R--;
}
// 如果和小于 0,说明需要增大和,左指针右移
else if (sum < 0) L++;
// 如果和大于 0,说明需要减小和,右指针左移
else if (sum > 0) R--;
}
}
// 返回最终结果
return ans;
};
📚 代码逐行解析
var threeSum = function(nums) {
let ans = []; // 存放最终结果
const len = nums.length;
if (nums == null || len < 3) return ans; // 边界判断
排序
nums.sort((a, b) => a - b); // 升序排序
- 排序是为了后面能更方便地控制双指针移动,并且可以用来去重。
外层循环:遍历每个可能作为第一个数的元素
for (let i = 0; i < len; i++) {
if (nums[i] > 0) break; // 因为已排序,如果第一个数大于0,则三数之和不可能为0
去重:如果当前数与前一个相同,跳过
if (i > 0 && nums[i] === nums[i - 1]) continue;
- 这一步确保不会把相同的第一个数重复处理,避免生成重复的三元组。
内部双指针查找 b 和 c
let L = i + 1;
let R = len - 1;
L
是左指针,从i+1
开始(因为不能重复使用同一个元素)R
是右指针,从数组末尾开始
双指针查找逻辑
while (L < R) {
const sum = nums[i] + nums[L] + nums[R];
- 计算当前三数之和
情况一:sum == 0
if (sum === 0) {
ans.push([nums[i], nums[L], nums[R]]); // 找到一个有效三元组
去重操作(跳过相同的 L 和 R 的值)
while (L < R && nums[L] === nums[L + 1]) L++; // 去重
while (L < R && nums[R] === nums[R - 1]) R--; // 去重
移动指针,继续寻找下一组
L++;
R--;
}
情况二:sum < 0
else if (sum < 0) L++; // 和太小,需要更大的数,所以 L 右移
情况三:sum > 0
else if (sum > 0) R--; // 和太大,需要更小的数,所以 R 左移
✅ 示例演示
输入:
nums = [-1, 0, 1, 2, -1, -4]
排序后变为:
[-4, -1, -1, 0, 1, 2]
外层循环依次取 -4
, -1
, -1
, 0
...
当 i=1
(第一个 -1
),L=2,R=5:
- sum = -1 + (-1) + 2 = 0 → 符合条件 → 加入结果
[-1, -1, 2]
- 继续移动 L 和 R,去重并继续查找
以此类推,最终输出:
[[-1, -1, 2], [-1, 0, 1]]
⏱️ 时间复杂度分析
- 排序:O(n log n)
- 双指针遍历:O(n²),外层循环 O(n),内层双指针最多 O(n)
总时间复杂度:O(n²)
空间复杂度:O(1)(不考虑结果存储)
🧩 总结
步骤 | 作用 |
---|---|
排序 | 方便去重、双指针查找 |
外层循环 | 固定第一个数 |
双指针 | 在剩余区间中查找两个数使和为 0 |
去重逻辑 | 避免重复的三元组 |
💡 小贴士
- 如果你想要用
Map
或Set
来优化查找,也可以实现,但会增加时间或空间开销。 - 对于同类问题(如四数之和),这种“排序 + 双指针”的思想仍然适用。