LeetCode热题100解析,NO.1 两数之和
一、标准解题逻辑步骤
-
理解题意
在数组nums中找到两个数,使它们的和等于target,返回这两个数的下标。
假设有且仅有一个有效解,且同一元素不能重复使用。 -
选择数据结构
用哈希表(JavaScript 中的Map)存储已经遍历过的数值及其下标,以便 O(1) 时间查找target - 当前值是否存在。 -
遍历数组
- 对每个
nums[i],计算complement = target - nums[i]。 - 检查
complement是否已经在Map中。 - 若在,直接返回
[map.get(complement), i]。 - 若不在,将
(nums[i], i)存入Map。
- 对每个
-
返回结果
由于题目保证有解,循环内一定会返回。
二、代码中调用的 API / 函数语法解析
1. new Map()
- 作用:创建一个空哈希表,键值对可以是任意类型。
- 参数:无(或可传入二维数组初始化)。
- 适用场景:需要快速查找、插入、删除键值对时。
2. map.has(key)
- 语法:
map.has(key)→ 返回boolean - 作用:判断
Map中是否存在某个键。 - 参数:要查找的键。
- 场景:检查
complement是否已被遍历过。
3. map.get(key)
- 语法:
map.get(key)→ 返回对应的值,若不存在则undefined - 作用:根据键取值。
- 参数:键名。
- 场景:获取
complement的下标。
4. map.set(key, value)
- 语法:
map.set(key, value)→ 返回Map对象本身(可链式调用) - 作用:添加或更新键值对。
- 参数:键、值。
- 场景:将当前数字及其下标存入哈希表,供后续查找。
三、得分重点 & 理解难点
| 类型 | 内容 |
|---|---|
| 得分重点 | 1. 使用哈希表将查找时间从 O(n²) 降到 O(n) 2. 理解“一次遍历 + 边查边存”的策略 3. 注意返回下标的顺序 |
| 理解难点 | 1. 为什么不用两次遍历? 2. 为什么要“先查后存”,而不是先存后查? 3. 如何避免使用同一个元素两次? |
重点解释:
先查后存:假如先存,当complement === nums[i](如[3,3],target=6),第一次遇到 3 时存进去,第二次遇到 3 时会查到刚刚存的自身吗?不会,因为查的时候还没存当前元素,所以不会出现同一个元素用两次的情况。
四、极易踩错的细节 & 边界错误
| 错误类型 | 说明 | 正确做法 |
|---|---|---|
| 忘记处理大数据范围 | 题目中数字可达 ±1e9,如果用数组模拟哈希会越界或超内存 | 用 Map,它不限制键的类型和范围 |
| 先存后查导致错误 | 如 nums=[3,2,4], target=6,先存 3,到 2 时查 complement=4 无,存 2,到 4 时查 complement=2 存在且是之前存的 2,返回正确。但若 [3,3] 先存第一个 3,第二个 3 查 complement=3 存在且是它自己?不会因为查的时候还没存当前元素,所以依然正确。实际上先存后查在某些情况会出错吗?试 [2,2], target=4:先存第一个2,第二个2查complement=2存在,返回 [0,1] 正确。那么先存后查有没有问题?有问题:若先存后查且目标值与当前值相同,比如 [3,3] target=6:先存3(下标0),第二个3查complement=3,存在,返回 [0,1] 也正确。那为什么官方解法都先查后存?主要是逻辑清晰且避免“使用同一个元素两次”的歧义。实际上先查后存严格避免了当前元素被自己匹配。 | 统一用“先查后存” |
| 忘记返回或返回 undefined | 循环外没有 return | 因为题目保证有解,但严谨起见可循环后 return [] |
| 下标顺序 | 题目允许任意顺序,但某些在线判题会严格检查第一个下标 < 第二个吗?不会,它们只要求数组内容一致 | 但建议返回 [较小下标, 较大下标] 避免争议 |
五、优化思路与代码对比
基础写法(暴力枚举)
var twoSum = function(nums, target) {
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²)
- 空间复杂度:O(1)
优化写法(哈希表)
var twoSum = function(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i];
if (map.has(complement)) {
return [map.get(complement), i];
}
map.set(nums[i], i);
}
};
- 时间复杂度:O(n)
- 空间复杂度:O(n)
更优的优化(无法低于 O(n),但可微调)
- 使用
Map的同时,可以尽早返回,不需要遍历完。 - 如果
nums极长且数字范围有限,可以用普通对象{}替代Map(但对象键会隐式转字符串,不适合数字很大的情况,且效率略低于 Map)。 - 真正的大数据优化:双指针法需要先排序,但会丢失原下标,除非带下标排序,实现更复杂,一般不如哈希表。
六、完整题解代码(附注释)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 哈希表:键 -> 数组元素值,值 -> 该元素的下标
const numMap = new Map();
// 一次遍历
for (let i = 0; i < nums.length; i++) {
const current = nums[i];
const complement = target - current;
// 检查 complement 是否已经在哈希表中
if (numMap.has(complement)) {
// 找到答案,返回 [complement的下标, 当前下标]
return [numMap.get(complement), i];
}
// 没找到,将当前元素存入哈希表,供后面的元素匹配
numMap.set(current, i);
}
// 题目保证有解,但为了语法完整,这里返回空数组
return [];
};