一、题目回顾
给定一个整数数组 nums 和一个目标值 target,
在数组中找出 两个数,使它们的和等于 target,并返回它们的 下标。
要求:
- 每个输入只会有一个解
- 同一个元素不能使用两次
示例:
nums = [2,7,11,15], target = 9
返回 [0,1]
二、最直观的思路:暴力枚举
最容易想到的办法是:
两层循环,枚举所有的数对
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
时间复杂度:O(n²)
问题在于:
- 每多一个元素,就多做大量无意义的比较
- 当 n 很大时,性能会迅速崩掉
所以关键问题是:
能不能在遍历数组的同时,快速判断
“有没有一个数,刚好能和当前数凑成 target”?
三、核心思想:把「找人」变成 O(1)
假设我们当前遍历到 nums[i],
想知道是否存在一个数 x,满足:
x + nums[i] = target
等价于:
x = target - nums[i]
所以问题就变成了:
我们之前见过
target - nums[i]吗?
这一步如果每次都遍历数组去找,还是 O(n)
但如果我们用一个 HashMap 记录已经遍历过的元素:
- key:数值
- value:下标
那么查询就可以做到 O(1)。
四、一步一步拆解 HashMap 解法
标准代码:
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> mp = new HashMap<>();
mp.put(nums[0], 0);
for (int i = 1; i < nums.length; i++) {
if (mp.containsKey(target - nums[i])) {
return new int[]{mp.get(target - nums[i]), i};
} else {
mp.put(nums[i], i);
}
}
return new int[]{0, 0};
}
}
下面逐行来看它在做什么,以及为什么这么写。
1. HashMap 里存的是什么?
HashMap<Integer,Integer> mp = new HashMap<>();
这里的含义是:
- key:数组中的某个数值
- value:这个数值对应的下标
也就是说:
mp 记录的是「已经遍历过的元素」
2. 为什么先放 nums[0]?
mp.put(nums[0], 0);
这是一个常见但容易被忽略的细节。
代码的整体策略是:
从左到右遍历数组
每次先“查找配对”,再“加入当前数”
所以在进入 for 循环前:
- 我们需要保证 HashMap 中 已经有一个元素
- 这样从
i = 1开始,就可以直接做判断
当然,也可以把这一步写进循环中(从 i = 0 开始),
这只是实现方式不同,思想完全一致。
3. 核心判断逻辑
if (mp.containsKey(target - nums[i])) {
return new int[]{mp.get(target - nums[i]), i};
}
这是整道题的灵魂。
含义是:
-
当前数是
nums[i] -
想找的另一个数是
target - nums[i] -
如果它已经出现在 HashMap 中
- 说明之前某个下标
j - 满足
nums[j] + nums[i] = target
- 说明之前某个下标
此时直接返回:
[j, i]
注意:
j < i,保证不会重复使用同一个元素- 顺序天然正确,不需要额外处理
4. 为什么找不到时才 put?
else {
mp.put(nums[i], i);
}
这是一个非常关键的设计点。
原因只有一个:
不能用同一个元素两次
如果我们先 put,再 containsKey,
那么当:
nums[i] * 2 == target
时,就可能错误地把同一个下标用两次。
而现在的顺序是:
- 只在「之前的元素」中找配对
- 当前元素只作为“未来的候选人”
逻辑上非常干净。
五、用一个例子走完整个过程
示例:
nums = [2, 7, 11, 15]
target = 9
初始化:
mp = {2 -> 0}
i = 1,nums[1] = 7
- target - 7 = 2
- mp.containsKey(2) == true
直接返回:
[0, 1]
算法结束。
六、复杂度分析
-
时间复杂度:
O(n)- 每个元素最多被 put 一次
- HashMap 查询是常数级
-
空间复杂度:
O(n)- 最坏情况下,所有元素都被存入 HashMap
这是当前问题的最优解法。
七、总结
这道题的本质不是「两数相加」,而是:
用空间换时间,把“查找”从 O(n) 降到 O(1)
关键点只有三个:
- 用 HashMap 记录「已经遍历过的数」
- 每次先找
target - nums[i] - 再把当前数放进 Map
理解了这套思路后,
你会发现它会反复出现在:
- 两数之和
- 三数之和(作为子步骤)
- 各类“配对 / 补数”问题中
不是模板,而是一种可以迁移的思维方式。