每日一题:LeetCode 1:两数之和——从暴力枚举到 Map 哈希表优化

「两数之和」是 LeetCode 中非常经典的一道数组题,主要考察数组遍历、哈希表 Map、差值查找以及时间复杂度优化。

这道题最重要的思路是:将寻找两个数的问题,转换成寻找另一个数是否存在的问题。

题目描述

给定一个整数数组 nums 和一个目标值 target,要求找到数组中两个数字,使它们相加等于 target,并返回对应的下标。

例如:

复制代码
nums = [2, 7, 11, 15]
target = 9

因为:

复制代码
nums[0] + nums[1] === 9
// 2 + 7 = 9

所以返回:

复制代码
[0, 1]

题目还有两个重要条件:每种输入保证只有一个有效答案,且不能重复使用同一个位置的元素。

因此,只要找到符合条件的一组下标,就可以立即返回。

暴力解法:双重 for 循环

最直接的思路是依次选择两个数字,判断它们的和是否等于 target。

复制代码
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]
      }
    }
  }
}

外层循环 i 负责选择第一个数字,内层循环 j 负责选择第二个数字。

这里需要注意:

复制代码
let j = i + 1

之所以从 i + 1 开始,是为了避免使用同一个数组元素,同时也不需要重复比较已经检查过的组合。

例如:

复制代码
nums = [2, 7, 11, 15]
target = 9

第一次比较:

复制代码
nums[0] + nums[1]
// 2 + 7 = 9

满足条件,直接:

复制代码
return [0, 1]

这种方式简单直观,但最坏情况下需要双重遍历,时间复杂度为 O(n²) ,额外空间复杂度为 O(1)。

优化思路:利用 Map 查找差值

暴力解法之所以效率较低,是因为每选择一个数字,都需要再次遍历数组寻找另一个数字。

其实可以换一种思路。

假设:

复制代码
target = 9
nums[i] = 2

那么另一个数字必须是:

复制代码
target - nums[i]
// 9 - 2 = 7

也就是说,我们不需要不断尝试两个数字相加,只需要判断:

当前数字所需要的另一个数字,之前是否出现过?

这时就可以使用 Map 哈希表。

Map 是什么?

Map 是 JavaScript 提供的键值对集合,可以通过 key 快速找到对应的 value。

例如:

复制代码
const map = new Map()

map.set(2, 0)
map.set(7, 1)

这里保存的是:

复制代码
数字 → 下标

2 → 0
7 → 1

常用方法如下:

方法 作用
new Map() 创建 Map
map.set(key, value) 保存键值对
map.has(key) 判断键是否存在
map.get(key) 获取键对应的值

例如:

复制代码
map.has(2) // true
map.get(2) // 0

在这道题中,使用 Map 保存已经遍历过的数字和对应下标,就能避免重复遍历数组。

Map 优化后的完整代码

javascript 复制代码
var twoSum = function(nums, target) {
  const map = new Map()

  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i]

    if (map.has(need)) {
      return [map.get(need), i]
    }

    map.set(nums[i], i)
  }
}

代码虽然很短,但包含三个关键操作。

首先:

复制代码
const need = target - nums[i]

计算当前数字还需要哪个数字,才能凑出目标值。

接着:

复制代码
if (map.has(need))

判断这个数字是否已经出现过。

如果存在,就通过:

复制代码
map.get(need)

获取它之前保存的下标,然后和当前下标 i 一起返回。

如果不存在,就执行:

复制代码
map.set(nums[i], i)

将当前数字和下标保存到 Map,供后续遍历使用。

结合示例理解执行过程

例如:

复制代码
nums = [2, 7, 11, 15]
target = 9

刚开始:

复制代码
map = new Map()

Map 为空。

当 i = 0 时:

复制代码
nums[i] = 2

need = 9 - 2
// 7

检查:

复制代码
map.has(7)
// false

由于 Map 中没有 7,所以保存当前数字:

复制代码
map.set(2, 0)

此时 Map 保存:

复制代码
2 → 0

当 i = 1 时:

复制代码
nums[i] = 7

need = 9 - 7
// 2

再次检查:

复制代码
map.has(2)
// true

说明之前已经遇到过 2。

通过:

复制代码
map.get(2)
// 0

拿到之前的下标,再结合当前下标:

复制代码
return [0, 1]

至此找到答案,函数执行结束。

为什么要先查找,再存入 Map?

注意代码顺序:

复制代码
if (map.has(need)) {
  return [map.get(need), i]
}

map.set(nums[i], i)

必须先查找,再存入当前数字,才能避免同一个元素被使用两次。

例如:

复制代码
nums = [3, 3]
target = 6

第一次遍历时,Map 为空,先保存:

复制代码
3 → 0

第二次遇到 3 时,才会找到之前的 3,因此返回:

复制代码
[0, 1]

两个数字虽然相同,但下标不同,符合题意。

为什么找到答案就直接 return?

因为题目明确保证:

每种输入只会对应一个有效答案。

因此:

复制代码
if (map.has(need)) {
  return [map.get(need), i]
}

找到后就不需要继续遍历。

不过,如果题目变成:

复制代码
nums = [2, 7, 3, 6]
target = 9

这里有两组符合条件的下标:

复制代码
2 + 7 = 9 → [0, 1]

3 + 6 = 9 → [2, 3]

原来的代码只会返回第一组找到的答案。

如果题目要求返回所有符合条件的下标组合 ,就不能直接 return,而且需要调整 Map 的存储方式,避免遗漏同一个数字对应的多个下标。

这也是做算法题时必须注意的一点:不同的题目条件,可能需要不同的处理方式。

时间复杂度和空间复杂度

两种解法对比如下:

解法 时间复杂度 空间复杂度
双重 for 循环 O(n²) O(1)
Map 哈希表 O(n) O(n)

Map 解法只需要遍历数组一次,而 Map.has()、Map.get()、Map.set() 的平均操作时间可以看作 O(1),因此整体平均时间复杂度是 O(n)。

不过,Map 最坏情况下需要存储接近 n 个元素,因此额外空间复杂度是 O(n)。

也就是说,Map 解法通过增加空间消耗,减少了重复遍历的时间。

总结

这道题最重要的优化思想,是把寻找两个数的问题转换成查找差值的问题。

最终代码:

javascript 复制代码
var twoSum = function(nums, target) {
  const map = new Map()

  for (let i = 0; i < nums.length; i++) {
    const need = target - nums[i]

    if (map.has(need)) {
      return [map.get(need), i]
    }

    map.set(nums[i], i)
  }
}

整体执行流程:

复制代码
遍历当前数字
      ↓
计算 need = target - nums[i]
      ↓
检查 Map 是否存在 need
      ↓
存在 → 返回之前下标和当前下标
      ↓
不存在 → 保存当前数字及其下标
      ↓
继续遍历

一句话记住:Map 保存已经遍历过的数字和下标,每遇到一个数字,就检查目标差值是否已经出现。

这道题不仅要掌握代码,更要理解其中的 空间换时间 思想,它也是哈希表在算法题中最经典的应用之一。

相关推荐
段一凡-华北理工大学1 小时前
高炉炼铁机器视觉与智能识别十八讲~系列文章09:AI 算法基础:从传统图像处理到深度学习的视觉“大脑“
图像处理·人工智能·算法·机器视觉·工业智能化·高炉炼铁智能化·高炉智能识别
alphaTao1 小时前
LeetCode 每日一题 2026/9/28-2026/10/4
算法·leetcode
源码学社1 小时前
图像去水印实测:OpenCV inpainting vs LaMa,传统算法和 AI 模型差距有多大
人工智能·opencv·算法·去水印·图片水印·ai去水印
一条大祥脚1 小时前
26ICPC网络赛第二场 计数|DFS|MEX|贪心
算法·深度优先·mex·icpc网络赛
aqiu1111111 小时前
【蓝桥杯 / C++】星际争霸 — 字符串逆序对与大整数自定义排序
算法
行者全栈架构师2 小时前
【鸿蒙心迹】ArkUI 列表性能实战——为什么 200 条数据页面掉到 20fps,LazyForEach 怎么救(HarmonyOS 7.x)
前端·算法·架构
一碗甜汤ᐝ2 小时前
高级算法分析与设计 | 常见 NP-Complete 问题总结
算法·np-complete·算法分析与设计·np完备问题
垆边人似月.2 小时前
分割数组的最大值最小化(200分 / 二分答案 + 贪心)
数据结构·算法·排序算法
CQU_JIAKE2 小时前
10.7【A】
算法