已是第一题LC2 →
LC1
两数之和 · 别暴力找, 先学会"记住见过的东西"
哈希表 · 一次遍历每扫到一个数字, 不是去后面乱找, 而是问: 我需要的另一半, 之前出现过吗?
输入 · nums
2i=07i=111i=215i=3
下标 0 先入仓库 下标 1 来匹配
能量门 · target
9
2 + 7 = 9 时开门
时间 O(n) · 每个元素只扫一次空间 O(n) · 哈希表存历史值核心: complement = target − nums[i]
先看暴力法翻车: 为什么需要哈希表
O(n²)暴力法 · 两层循环
2→ 试探71115
7→ 试探1115
11→ 试探15
每个数字都要重新扫一遍后面的数字。 n=4 还好, n=10⁴ 时要做 10⁸ 次比对 —— 路径乱、重复多, 大数据直接超时。
O(n)哈希优化 · 边走边记
- 1.开一个"记忆仓库" seen (value → index)。
- 2.扫到 nums[i] 时, 先算 need = target − nums[i]。
- 3.查 seen 里有没有 need —— 有就立刻配对收工。
- 4.没有就把 nums[i] 存入 seen, 下一个数来配它。
每个数字只参与"一次查 + 一次存", 总共 2n 次操作, 所以是 O(n)。
登场 · 看清题目初始化Step 1 / 6
执行舱 · 扫描机器人 + 记忆仓库
无扫描中 · 教学帧数组传送带 · nums
- i=02
- i=17
- i=211
- i=315
扫描机器人 · complement 雷达
current = nums[i]
—
complement = target − current
等待扫描
能量门 target
9
待两个数加和 = 9
Seen Map · 记忆仓库
0 条仓库还空着 — 还没扫到任何数。
仓库只持有"已经扫过"的元素 (nums[0..i-1])。 所以查到的下标永远 < i, 不会跟自己配对。
这一步在做什么 · 人话版
题目给我们一组数字 nums = [2, 7, 11, 15] 和 target = 9。要找到两个数, 它们的下标加起来值为 9。我们的策略不是暴力两两试, 而是开一个"记忆仓库" seen, 一边扫一边记 —— 每扫到一个数, 先问"我需要的另一半之前出现过吗?"
状态面板 · 这一帧的全部变量
i
—
current = nums[i]
—
complement
—
map has complement?
—
action
初始化
Go 解法 · 与动画同步
高亮行: 21func twoSum(nums []int, target int) []int {2 seen := map[int]int{}34 for i, x := range nums {5 need := target - x67 if j, ok := seen[need]; ok {8 return []int{j, i}9 }1011 seen[x] = i12 }1314 return nil15}
蓝色 = 查询补数 · 绿色 = 命中返回 · 紫色 = 存入仓库。
步骤列表
点击跳转30 秒面试回答
我用哈希表记录已经扫描过的数字和下标。遍历到 nums[i] 时, 计算 need = target − nums[i], 如果 need 已经在 map 中, 说明之前的某个数和当前数能凑成 target, 直接返回两个下标。否则把当前数字存入 map。 这样每个元素只扫描一次, 时间 O(n), 空间 O(n)。 关键点是先查再存, 避免当前元素和自己配对。
常见误区 · 别再翻车
- 先存后查, 导致自己和自己配对把 seen[x] = i 写在查询之前, 当 nums[i] 恰好等于 target/2 时, seen 里立刻就有 need = x, 程序错误地返回 [i, i]。永远先 if seen[need], 再 seen[x] = i。
- 只返回值, 不返回下标LC1 要求返回的是下标对 [j, i], 不是值对 [nums[j], nums[i]]。所以 seen 必须用 value → index 这种映射, 而不是 set。
- 以为必须排序排序后可以用双指针 (LC167 的做法), 但 LC1 要求返回原数组下标。排序会破坏原始下标位置, 你得额外维护 [value, originalIndex] 才能恢复, 反而更麻烦。
- 把哈希表当成排好序的结构Go 的 map / Java 的 HashMap 不保证遍历顺序, 也不能按 key 做范围查询。LC1 只需要等值查找, 所以哈希表足够; 如果题目变成「两数之差」, 那就要换数据结构。