LeetCode 1. 两数之和:两种高效解法(双指针 + Map)

0 阅读5分钟

今天拆解 LeetCode 入门必刷第一题——两数之和,这道题看似简单,却能帮我们理清双指针、哈希表两种核心解题思路,还能避开不少新手容易踩的坑。话不多说,先看题目!

一、题目描述(LeetCode 1. 两数之和)

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

📌 关键约束:

  • 每种输入只会对应一个答案(不用考虑多解情况);

  • 不能使用两次相同的元素(即同一个下标对应的元素不能用两次);

  • 答案可以按任意顺序返回。

💡 示例:

输入:nums = [2,7,11,15], target = 9 → 输出:[0,1](因为 2+7=9,对应下标 0 和 1)

输入:nums = [3,2,4], target = 6 → 输出:[1,2](注意不是 [0,0],不能重复使用元素)

二、解法一:双指针解法(时间复杂度 O(n log n),需处理索引问题)

1. 解题思路

核心思路:排序 + 双指针。先将数组排序,再用左右两个指针分别指向数组两端,通过调整指针位置找到和为 target 的两个元素,最后返回它们的原始下标。

步骤拆解:

  1. 保存原始索引:因为排序会打乱数组的原始下标,所以先创建一个包含「元素值 + 原始下标」的对象数组;

  2. 排序:对新数组按元素值从小到大排序;

  3. 双指针遍历:左指针(left)指向开头(最小元素),右指针(right)指向结尾(最大元素),计算两元素之和:

  4. 和 = target:找到答案,返回两个元素的原始下标;

  5. 和 < target:左指针右移(增大元素值,让和变大);

  6. 和 > target:右指针左移(减小元素值,让和变小)。

2. 完整代码(TypeScript)

function twoSum_2(nums: number[], target: number): number[] {
  // 1. 创建包含数值和原始索引的数组,避免排序打乱原始索引
  const numsWithIndex = nums.map((num, index) => ({ num, index }));

  // 2. 按数值大小排序(仅排序新数组,不修改原数组)
  numsWithIndex.sort((a, b) => a.num - b.num);

  // 3. 初始化双指针
  let left = 0;
  let right = numsWithIndex.length - 1;

  // 4. 双指针遍历,寻找和为 target 的两个元素
  while (left < right) {
    const sum = numsWithIndex[left].num + numsWithIndex[right].num;
    if (sum === target) {
      // 返回原始索引(不是排序后的指针位置!)
      return [numsWithIndex[left].index, numsWithIndex[right].index];
    } else if (sum < target) {
      // 和偏小,左指针右移,增大数值
      left++;
    } else {
      // 和偏大,右指针左移,减小数值
      right--;
    }
  }

  // 题目约束有唯一解,此处仅为语法兼容
  return [];
}

3. 关键细节 & 避坑点

  • ⚠️ 新手最容易踩的坑:直接排序原数组!排序会打乱原始下标,导致最后返回的指针位置不是题目要求的「原始下标」,所以必须先保存原始索引;

  • 排序的时间复杂度:数组排序的时间复杂度是 O(n log n),后续双指针遍历是 O(n),整体时间复杂度由排序决定;

  • 双指针的优势:空间复杂度低(O(n),仅用于保存索引数组),思路直观,适合对空间复杂度有要求,但对时间复杂度要求不极致的场景。

三、解法二:哈希表(Map)解法(最优解,时间复杂度 O(n))

1. 解题思路

核心思路:用空间换时间,遍历数组时,记录每个元素的「值」和「下标」,同时判断「目标值 - 当前元素」是否已经在哈希表中。

举个例子:target = 9,当前元素是 2(下标 0),那么我们需要找 9-2=7。此时哈希表中没有 7,就把 2 和下标 0 存入哈希表;继续遍历到 7(下标 1),找 9-7=2,发现 2 已经在哈希表中,直接返回 [2 的下标, 7 的下标] 即可。

优势:只需遍历一次数组,时间复杂度从暴力解法的 O(n²) 降到 O(n),是这道题的最优解,弥补了双指针解法时间复杂度较高的不足。

2. 完整代码(TypeScript)

function twoSum_1(nums: number[], target: number): number[] {
  // 定义 Map,key 存数组元素值,value 存对应下标
  const map = new Map<number, number>();
  // 遍历数组,i 是当前元素下标,nums[i] 是当前元素值
  for (let i = 0; i < nums.length; i++) {
    // 计算需要找的互补数(目标值 - 当前元素)
    const complement = target - nums[i];
    // 判断互补数是否已经在 Map 中(存在即找到答案)
    if (map.has(complement)) {
      // 返回互补数的下标 和 当前元素下标
      return [map.get(complement)!, i];
    }
    // 若不存在,将当前元素和下标存入 Map,供后续查找
    map.set(nums[i], i);
  }
  // 题目约束有唯一解,这里仅为语法兼容,实际不会执行
  return [];
};

3. 关键细节 & 避坑点

  • Map 的作用:快速查询「互补数」是否存在,查询时间复杂度是 O(1),这是提升效率的核心;

  • 为什么先判断再存入?避免「重复使用同一个元素」。比如 nums = [3,3], target = 6,遍历第一个 3 时,Map 中没有 3,存入;遍历第二个 3 时,找 6-3=3,此时 Map 中有 3(第一个 3 的下标),直接返回 [0,1],不会出现重复使用的情况;

  • Map.get(complement)! 中的「!」:TypeScript 语法,告诉编译器 complement 一定在 Map 中(题目约束有唯一解),避免编译报错。

四、两种解法对比

解法时间复杂度空间复杂度核心优势适用场景
双指针O(n log n)O(n)空间利用率高,思路直观对空间有要求,或后续需要排序的延伸场景
哈希表(Map)O(n)O(n)效率最高,一次遍历搞定大多数情况(推荐首选)

五、总结

两数之和作为 LeetCode 入门题,核心考察「双指针的区间调整」和「哈希表的快速查询」两种思路,重点记住:

  1. 双指针解法要注意「保存原始索引」,避免排序打乱下标,时间复杂度由排序决定;

  2. 哈希表解法是最优解,时间复杂度 O(n),关键是「用 Map 保存元素和下标,找互补数」;

  3. 解题时先看题目约束(如“唯一解”“不重复使用元素”),这些约束会帮我们简化逻辑、避开坑。