引言
“面试官最怕什么?不是你不会做题,而是你只会背答案。”
—— 来自一位大厂算法面试官的真实吐槽
今天我们要聊的,是 LeetCode 上最经典、也最“有毒”的题目之一:1. 两数之和(Two Sum)** 。别看它标着“简单”,但它是无数人进入大厂的第一道门槛,也是算法思维启蒙的起点。
在这篇文章中,我们将:
- 深入解析这道题的核心逻辑
- 逐行解读代码,添加详细注释
- 全面讲解哈希表(Hash Map) 的工作原理
- 揭秘大厂面试官真正想考察的是什么
- 给你一套高分表达策略,让你在面试中脱颖而出!
题目回顾:两数之和
给定一个整数数组 nums 和一个目标值 target,请你找出数组中两个数,使得它们的和等于 target,并返回这两个数的下标。
示例:
输入: nums = [2,7,11,15], target = 9
输出: [0,1]
解释: nums[0] + nums[1] == 9
看起来很简单?没错,但正是这种“简单题”,最能暴露你的算法思维深度。
解法对比:暴力 vs 哈希表
暴力解法:双重循环(O(n²))
for (let i = 0; i < nums.length; i++) {
for (let j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] === target) {
return [i, j];
}
}
}
这个方法虽然直观,但时间复杂度是 O(n²) ,当数组很大时,性能会急剧下降。比如有 1000 个元素,就要比较近百万次!😱
优化解法:哈希表(O(n))
这才是真正的“大厂风格”——用空间换时间,把查找效率提升到极致!
我们来看这段原汁原味的代码,并在每一行都加上详细的解析:
function twoSum(nums, target) {
// nums是一个整数数组,每个元素都不同
// target是一个整数,表示目标值
// es6 提供了hashMap 很重要
// 语言都提供了一些内置的数据结构 数组[] 哈希表{}
// diffs 哈希表 用来存储数组中的元素和它们的索引
const diffs = new Map(); // es6 没有hashMap O(1)时间复杂度
const len = nums.length; // 缓存数组的长度
// 遍历数组
for (let i = 0; i < len; i++) {
// 计算当前元素的补数
const complement = target - nums[i]; // 和变成差
// 检查补数是否已存在于哈希表中
if (diffs.has(complement)) {
// 如果存在,则返回补数的索引和当前元素的索引
return [diffs.get(complement), i];
}
// 如果不存在,则将当前元素和它的索引存入哈希表
diffs.set(nums[i], i);
// diffs[nums[i]] = i; // 这样写也可以,但不推荐,因为对象键名限制
}
}
console.log(twoSum([2, 7, 11, 15], 9));
逐句解析:代码背后的智慧
让我们一行一行拆解这段代码,看看它到底做了什么。
第一步:定义变量
const diffs = new Map();
👉 这里使用了 ES6 的 Map 对象作为哈希表。为什么不用普通对象 {}?
- 因为
Map支持任意类型的键(包括对象、函数等),而普通对象只能用字符串或符号作键。 - 更重要的是,
Map的set()和get()方法更清晰,且性能稳定。 - 而且
Map是专门设计来模拟“哈希表”行为的,更适合算法题。
const len = nums.length;
👉 缓存数组长度,避免每次循环都调用 .length,虽然现代引擎已经优化了这一点,但这是一个良好的编程习惯。
第二步:遍历数组
for (let i = 0; i < len; i++) {
👉 单次遍历,这是关键!我们不再需要嵌套循环了。
第三步:计算“补数”
const complement = target - nums[i]; // 和变成差
🎯 这是整个算法的灵魂所在!
把“找两个数加起来等于 target” → 转化成“找一个数等于 target 减去当前数”。
举个例子:
如果 target = 9,当前 nums[i] = 2,那么我们需要找的是 9 - 2 = 7。
如果我们之前见过 7,那就可以直接返回了!
这就是所谓的 “求和变求差” ,是算法思维的一大跃迁。
第四步:检查补数是否存在
if (diffs.has(complement)) {
return [diffs.get(complement), i];
}
👉 has() 方法判断某个键是否存在,时间复杂度是 O(1) 。
👉 get() 获取对应键的值(也就是索引)。
✅ 找到了就立刻返回结果,不需要继续遍历。
第五步:存入哈希表
diffs.set(nums[i], i);
👉 把当前数字和它的索引存进哈希表,等待后续匹配。
⚠️ 注意顺序:先查后存!
为什么不能先存再查?
因为如果先存,可能会出现自己跟自己配对的情况(比如 [3,3],target=6),但我们要求不能重复使用同一个元素。
所以必须是:先查有没有补数,再把自己存进去。
哈希表:数据结构界的“超级英雄”
什么是哈希表?
哈希表(Hash Table)是一种基于键值对(key-value) 的数据结构,支持快速插入、删除和查找操作。
它的工作原理如下:
- 输入一个“键”(key)
- 通过一个叫做“哈希函数”(hash function)的算法,把这个键转换成一个唯一的索引
- 将对应的“值”(value)放在这个索引位置上
- 查找时,再次用哈希函数定位,就能瞬间找到值
⚡️ 平均时间复杂度:O(1) ,比数组的 O(n) 快得多!
JavaScript 中的哈希表实现
| 数据结构 | 是否支持任意键 | 是否有序 | 推荐场景 |
|---|---|---|---|
Object | ❌ 只支持字符串/符号 | ❌ 无序 | 简单映射 |
Map | ✅ 支持任意类型 | ✅ 有序 | 算法题首选 |
所以在本题中,我们选择 new Map() 是最优解。
核心思想总结:三句口诀
记住这三句话,你在任何面试中都能自信应对:
🔹 用哈希 map
🔹 空间换时间
🔹 求和变求差
这不仅是解题技巧,更是算法工程师的思维方式。
学习建议:刷题 ≠ 背答案
正确的学习路径:
- 刷 LeetCode Hot 100,至少刷三遍
- 每道题都要理解其背后的数据结构与算法思想
- 不仅要会写代码,还要能讲清楚为什么这么写
- 主动对比不同解法的时间/空间复杂度
错误的做法:
- 只背代码模板,不理解逻辑
- 遇到难题就跳过,不去思考
- 写完就不管,没有复盘
实战演示:一步一步走一遍
以 nums = [2,7,11,15], target = 9 为例:
| i | nums[i] | complement | diffs 中是否有? | 操作 |
|---|---|---|---|---|
| 0 | 2 | 9-2=7 | 否 | 存入 {2:0} |
| 1 | 7 | 9-7=2 | 是!(2 在 index 0) | 返回 [0,1] ✅ |
完美命中!
总结:不只是解一道题
《两数之和》看似简单,但它教会了我们:
- 如何用哈希表解决查找问题
- 如何进行思维转换(求和 → 求差)
- 如何权衡时间和空间复杂度
- 如何在面试中清晰表达思路
这些能力,远比写出一段正确的代码更重要。
最后送你一句金句:
“算法不是为了炫技,而是为了解决问题。”
当你能在 1 分钟内说出“用哈希表,空间换时间,求和变求差”这三句话时,你就已经迈过了大多数人的门槛。
现在,打开你的编辑器,把这段代码敲一遍,然后大声念出那三句话吧!