你以为你在刷题?不,你是在给数组做相亲!

12 阅读4分钟

在力扣(LeetCode)的世界里,数组题就像打怪升级:

  • 两数之和 是新手村小怪;
  • 三数之和 开始让你怀疑人生;
  • 四数之和 直接劝退一半选手;
  • 四数相加 II?那根本不是人做的,是给哈希表开后门的!

但别慌!今天我就带你用 一套思维 + 三种武器,轻松打通这套“数组连连看”系列题,顺便笑出腹肌(虽然可能只是错觉)。


🧩 第一关:两数之和 —— “找对象”问题

题目:给你一个数组和一个目标值,找出两个数加起来等于目标值,并返回它们的下标。

这题简直是程序员版《非诚勿扰》:

“请问,有没有人能和我凑成 target?”
“有!我在 Map 里等你很久了。”

✅ 解法核心:哈希表(Map)

我们遍历数组,对每个数字 num,立刻问一句:“有没有人是 target - num?”
如果有,恭喜配对成功!没有?那就把自己登记进“单身信息库”(Map),等待未来的缘分。

var twoSum = function(nums, target) {
    const index = new Map();
    for (let i = 0; i < nums.length; i++) {
        let need = target - nums[i];
        if (index.has(need)) return [index.get(need), i];
        index.set(nums[i], i); // 先登记,再等缘
    }
};

💡 思考点:为什么不用双重循环?因为 O(n²) 太慢,而 Map 让我们 O(1) 查对象,效率拉满!


🔥 第二关:三数之和 —— “三人行,必有零和”

题目:找出所有不重复的三元组,使得它们的和为 0。

这时候,“找对象”升级成了“组队打副本”。但有个坑:不能有重复队伍!

✅ 解法核心:排序 + 双指针 + 去重三连

  1. 先排序:让数组变得有序,方便控制左右指针;
  2. 固定第一个数(i),然后在右边用双指针找另外两个;
  3. 跳过重复值:避免 [ -1, -1, 2 ] 出现两次。
function threeSum(nums) {
    nums.sort((a, b) => a - b);
    const res = [];
    for (let i = 0; i < nums.length - 2; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue; // 跳过重复起点
        let left = i + 1, right = nums.length - 1;
        while (left < right) {
            const sum = nums[i] + nums[left] + nums[right];
            if (sum === 0) {
                res.push([nums[i], nums[left], nums[right]]);
                left++; right--;
                // 跳过重复的 leftright
                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;
}

😅 幽默时刻

这题最怕什么?不是找不到解,而是找到一堆“孪生兄弟”——[-1,0,1][-1,0,1] 看起来一样,但系统说:“不行,你们是同一个人!”


💣 第三关:四数之和 —— “四人麻将,胡牌条件:和为 target”

题目:找出所有不重复的四元组,和为 target。

别怕!它只是 三数之和套了个壳
思路:两层 for 循环固定前两个数,后面用双指针找剩下两个

var fourSum = function(nums, target) {
    nums.sort((a, b) => a - b);
    const res = [];
    if (nums.length < 4) return [];
    for (let i = 0; i < nums.length - 3; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        for (let j = i + 1; j < nums.length - 2; j++) {
            if (j > i + 1 && nums[j] === nums[j - 1]) continue;
            let left = j + 1, right = nums.length - 1;
            while (left < right) {
                const sum = nums[i] + nums[j] + nums[left] + nums[right];
                if (sum === target) {
                    res.push([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--;
                } else if (sum < target) left++;
                else right--;
            }
        }
    }
    return res;
};

🎯 关键洞察

  • 时间复杂度 O(n³),但通过排序+去重,实际运行快很多;
  • 如果 target 很大或很小,可以加剪枝(比如提前 break),但面试时先保证正确性!

🎯 终极彩蛋:四数相加 II —— “跨服组队,不限下标!”

题目:四个数组,各选一个数,问有多少种组合能让和为 0。

注意!这题 不要求下标不同,也不在一个数组里!所以 不能用双指针,但可以用 哈希表分治

✅ 解法核心:分两组 + 哈希计数

  • 把 nums1 和 nums2 的所有两数之和存进 Map,记录出现次数;
  • 再遍历 nums3 和 nums4,看 -(c + d) 是否在 Map 中;
  • 如果有,就把对应的次数加到答案里。
var fourSumCount = function(nums1, nums2, nums3, nums4) {
    const map = new Map();
    let count = 0;
    // 第一步:统计 A + B 的所有可能
    for (let a of nums1) {
        for (let b of nums2) {
            const sum = a + b;
            map.set(sum, (map.get(sum) || 0) + 1);
        }
    }
    // 第二步:找 -(C + D)
    for (let c of nums3) {
        for (let d of nums4) {
            const need = -(c + d);
            if (map.has(need)) count += map.get(need);
        }
    }
    return count;
};

🧠 为什么聪明?

  • 时间从 O(n⁴) 降到 O(n²)!
  • 空间换时间,典型的“拿内存换命”策略(程序员的续命良方)。

🧠 总结:数组题的“万能套路”

题型核心思想数据结构时间复杂度
两数之和找补集哈希表O(n)
三数/四数之和排序 + 双指针 + 去重数组 + 双指针O(n²)/O(n³)
四数相加 II分治 + 哈希计数MapO(n²)

记住一句话

  • 同一个数组里找组合 → 排序 + 双指针
  • 多个数组里找组合 → 哈希分治

💬 最后说两句

刷题不是为了背答案,而是为了培养“看到问题就想到模式”的直觉
当你下次看到“和为 X”、“不重复”、“多个数组”这些关键词,脑子里自动跳出“哈希 or 双指针”,你就赢了!

别人还在暴力 for 循环,你已经优雅地用 Map 秒杀了。