从两数之和到三数之和:双指针的优雅艺术

72 阅读3分钟

“面试官问三数之和,我只用了 10 行核心逻辑。”

在 LeetCode 的浩瀚题海中,有两道题堪称双指针算法的启蒙导师——
👉 167. 两数之和 II - 输入有序数组
👉 15. 三数之和

它们看似简单,却藏着去重、剪枝、边界控制三大高频考点。今天,我们就用前端工程师的视角,拆解这两道题的精华逻辑,并提炼出一套可复用的双指针模板


🔍 一、两数之和 II:有序数组的最优解

题目要求:在一个升序排列的整数数组中,找到两个数,使其和等于 target,返回它们的 1-based 索引

✅ 核心思想:左右夹逼

因为数组已排序,我们可以:

  • 左指针 left 从开头出发(最小值)
  • 右指针 right 从末尾出发(最大值)
  • 若和太大 → right--;若和太小 → left++
/**
 * @param {number[]} numbers
 * @param {number} target
 * @return {number[]}
 */
var twoSum = function(numbers, target) {
    let left = 0;
    let right = numbers.length - 1;
    while(left < right){
        const sum = numbers[left] + numbers[right];
        if(sum == target){
            break;
        }
        if(sum > target){
            right--;
        }
        else{
            left++
        }
        
    }
    return [left+1,right+1];
};

image.png

💡 为什么这题能用双指针?
因为单调性!数组有序 ⇒ 和具有方向性 ⇒ 指针移动有明确策略。


🧩 二、三数之和:从 O(n³) 到 O(n²) 的飞跃

题目升级:在无序数组中找出所有不重复的三元组,使得 a + b + c = 0

暴力解法是三层循环,时间复杂度 O(n³),显然不行。
但如果我们先排序,就能把问题转化为 “固定一个数 + 两数之和”

🎯 解题框架:

  1. 排序nums.sort((a, b) => a - b)
  2. 外层循环:枚举第一个数 nums[i]
  3. 内层双指针:在 [i+1, n-1] 区间找两数之和为 -nums[i]

✅ 完整实现(含去重 + 剪枝):

💡 为什么排序后不会漏解?
因为我们枚举的是而非原始索引,只要三元组的值存在,排序后一定能找到。


🧠 三、升华:双指针的通用模板

通过这两题,我们可以抽象出一个双指针处理“和类问题”的通用模式

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
/**
 * 找出数组中所有不重复的三元组,使得三数之和为 0。
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    nums.sort((a, b) => a - b); // 升序排序
    const n = nums.length;
    const ans = [];

    for (let i = 0; i < n - 2; i++) {
        const x = nums[i];
        
        // 跳过重复的第一个数
        if (i > 0 && x === nums[i - 1]) continue;
        
        // 最小可能和 > 0,后续不可能有解
        if (x + nums[i + 1] + nums[i + 2] > 0) break;
        
        // 最大可能和 < 0,当前 x 太小,跳过
        if (x + nums[n - 2] + nums[n - 1] < 0) continue;

        let j = i + 1;
        let k = n - 1;

        while (j < k) {
            const s = x + nums[j] + nums[k];
            if (s > 0) {
                k--;
            } else if (s < 0) {
                j++;
            } else {
                // 找到一个解
                ans.push([x, nums[j], nums[k]]);
                
                // 跳过重复的 nums[j]
                while (j < k && nums[j] === nums[j + 1]) j++;
                // 跳过重复的 nums[k]
                while (j < k && nums[k] === nums[k - 1]) k--;
                
                // 移动双指针,寻找下一组可能解
                j++;
                k--;
            }
        }
    }

    return ans;
};

image.png 这套逻辑还能扩展到:

  • 四数之和(LeetCode 18)
  • 最接近的三数之和(LeetCode 16)
  • 三角形计数(LeetCode 611)

🏁 结语:算法不是背题,而是建模

很多前端同学觉得算法“离业务远”,但其实:

  • 双指针 ≈ 滑动窗口 ≈ 快慢指针,都是“状态压缩”的思想;
  • 去重与剪枝 ≈ 性能优化,是你写高效 React/Vue 应用的底层能力;
  • 边界控制 ≈ 健壮性,正是优秀工程师的标志。

下次面试官再问“三数之和”,你不仅能写出代码,还能说出:“我用了排序 + 双指针 + 两级去重 + 双剪枝,时间复杂度 O(n²),空间 O(1)。”

这,就是技术深度


互动时间:你在刷 LeetCode 时,还遇到过哪些“看似简单却暗藏玄机”的题目?欢迎评论区分享!