NO.1 两数之和——LeetCode 热题 100

13 阅读5分钟

LeetCode热题100解析,NO.1 两数之和

一、标准解题逻辑步骤

  1. 理解题意
    在数组 nums 中找到两个数,使它们的和等于 target,返回这两个数的下标。
    假设有且仅有一个有效解,且同一元素不能重复使用。

  2. 选择数据结构
    用哈希表(JavaScript 中的 Map)存储已经遍历过的数值及其下标,以便 O(1) 时间查找 target - 当前值 是否存在。

  3. 遍历数组

    • 对每个 nums[i],计算 complement = target - nums[i]
    • 检查 complement 是否已经在 Map 中。
    • 若在,直接返回 [map.get(complement), i]
    • 若不在,将 (nums[i], i) 存入 Map
  4. 返回结果
    由于题目保证有解,循环内一定会返回。


二、代码中调用的 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 [];
};