LeetCode 热题 100 之第6题 三数之和(JavaScript篇)

4 阅读4分钟

传送门:15. 三数之和 - 力扣(LeetCode)

🔍 题目要求回顾:

给定一个数组 nums,找出所有满足条件的三元组 [a, b, c],使得:

  • a + b + c = 0
  • 三元组不能重复(例如 [1, -1, 0][0, 1, -1] 被视为同一个组合)

🧠 解题思路

这个问题的核心在于如何高效地找到所有满足条件的三元组,并且避免重复。

✅ 使用“排序 + 双指针”策略:

  1. 排序:先对数组进行排序,方便后续去重和双指针查找。
  2. 固定第一个数 a = nums[i],然后在剩下的部分中使用双指针找两个数 b 和 c,使得 a + b + c == 0
  3. 去重逻辑
    • 如果当前固定值 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
去重逻辑避免重复的三元组

💡 小贴士

  • 如果你想要用 MapSet 来优化查找,也可以实现,但会增加时间或空间开销。
  • 对于同类问题(如四数之和),这种“排序 + 双指针”的思想仍然适用。