🚀 三数之和:从暴力到双指针,我悟了!

76 阅读3分钟

“两数之和我还能暴力,三数之和?那不得 O(n³) 爆炸?”
别慌!今天带你用排序 + 双指针,把三数之和从“地狱难度”变成“轻松拿捏”。


🧠 问题描述

给定一个整数数组 nums,判断是否存在三个元素 a, b, c,使得 a + b + c = 0
要求:找出所有不重复的三元组。

例如:

输入: nums = [-1, 0, 1, 2, -1, -4]
输出: [[-1, -1, 2], [-1, 0, 1]]

注意:结果中不能包含重复的三元组!


💥 暴力解法:O(n³) 的“青春疼痛”

最直觉的做法?三层 for 循环,枚举所有可能的三元组:

for (let i = 0; i < n; i++)
  for (let j = i + 1; j < n; j++)
    for (let k = j + 1; k < n; k++)
      if (nums[i] + nums[j] + nums[k] === 0) ...

时间复杂度:O(n³)
空间复杂度:O(1)

但现实很骨感——LeetCode 直接给你一个超时大礼包 💣。
而且去重?想想就头大:怎么保证 [ -1, 0, 1 ][ 0, -1, 1 ] 不算两次?

所以,是时候请出我们的主角了 👉 排序 + 双指针


🌟 核心思想:先排好队,再“夹击”

第一步:排序(O(n log n))

为什么排序?因为:

  • 有序数组能让我们用双指针“聪明地”移动
  • 重复元素会挨在一起,方便跳过
nums.sort((a, b) => a - b); // 升序排列

小知识:JavaScript 的 Array.sort() 默认按字符串比较,所以必须传比较函数!


第二步:固定一个数,双指针找另外两个

想象你在玩“三明治”游戏:

  • 最左边是固定的“底层面包”(nums[i]
  • 中间夹着“生菜”(left)和“火腿”(right
  • 目标:让三者加起来正好等于 0!

具体操作:

  1. 遍历 i0n-3(因为后面至少要留两个位置)

  2. 跳过重复的 nums[i](避免重复三元组)

  3. 设置 left = i + 1right = n - 1

  4. 计算 sum = nums[i] + nums[left] + nums[right]

    • sum == 0 → 找到答案!加入结果,并同时移动左右指针,还要跳过重复值
    • sum < 0 → 太小了,left++(增大)
    • sum > 0 → 太大了,right--(减小)

✨ 代码实现(带注释版)

function threeSum(nums) {
  // 1. 排序!这是魔法的开始
  nums.sort((a, b) => a - b);
  const res = [];

  // 2. 固定第一个数
  for (let i = 0; i < nums.length - 2; i++) {
    // 跳过重复的起点(避免重复三元组)
    if (i > 0 && nums[i] === nums[i - 1]) continue;

    let left = i + 1;
    let right = nums.length - 1;

    // 3. 双指针夹击
    while (left < right) {
      const sum = nums[i] + nums[left] + nums[right];

      if (sum === 0) {
        res.push([nums[i], nums[left], nums[right]]);

        // 关键:找到后继续找,但要跳过重复!
        left++;
        right--;

        // 跳过左边重复
        while (left < right && nums[left] === nums[left - 1]) left++;
        // 跳过右边重复
        while (left < right && nums[right] === nums[right + 1]) right--;
      } 
      else if (sum < 0) {
        left++; // 太小,往右走
      } 
      else {
        right--; // 太大,往左走
      }
    }
  }

  return res;
}

🎯 为什么这样不会漏解?

因为数组已排序,对于固定的 nums[i]

  • 如果 sum < 0,说明当前 left 太小,只有增大 left 才可能接近 0
  • 如果 sum > 0,说明 right 太大,只有减小 right 才可能接近 0

这就像在一条有序的数轴上,用两个指针“逼近”目标值,每一步都朝着正确方向前进,绝不错过任何可能!


⏱️ 时间 & 空间复杂度

  • 时间复杂度:O(n log n)(排序) + O(n²)(外层循环 × 双指针) → O(n²)
  • 空间复杂度:O(1)(不考虑结果数组)

比暴力快了一个数量级!👏


😂 幽默小剧场

面试官:“你会做三数之和吗?”
我:“会!三层 for 循环,优雅又简洁。”
面试官:“……下一位。”

别做那个“下一位”!学会双指针,你就是面试官眼中的算法锦鲤 🐟。


🔚 总结

方法时间复杂度是否可行适合场景
暴力三重循环O(n³)❌ 超时面试装傻(不推荐)
排序 + 双指针O(n²)✅ 优雅高效实战、面试、刷题

记住口诀

“三数之和先排序,固定一头双指找;
小了左移大右缩,重复跳过不烦恼。”


📌 最后

算法不难,难的是迈出第一步。而你,已经走在路上了。