在力扣(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。
这时候,“找对象”升级成了“组队打副本”。但有个坑:不能有重复队伍!
✅ 解法核心:排序 + 双指针 + 去重三连
- 先排序:让数组变得有序,方便控制左右指针;
- 固定第一个数(i),然后在右边用双指针找另外两个;
- 跳过重复值:避免
[ -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--;
// 跳过重复的 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;
}
😅 幽默时刻:
这题最怕什么?不是找不到解,而是找到一堆“孪生兄弟”——
[-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 | 分治 + 哈希计数 | Map | O(n²) |
记住一句话:
- 同一个数组里找组合 → 排序 + 双指针;
- 多个数组里找组合 → 哈希分治!
💬 最后说两句
刷题不是为了背答案,而是为了培养“看到问题就想到模式”的直觉。
当你下次看到“和为 X”、“不重复”、“多个数组”这些关键词,脑子里自动跳出“哈希 or 双指针”,你就赢了!
别人还在暴力 for 循环,你已经优雅地用 Map 秒杀了。