两数之和:从暴力到哈希表法
(力扣第1题:原来找两个数也能这么简单!)
问题本质:找两个数加起来等于目标值
给一个数组 nums 和一个目标值 target,找出两个数,它们的和刚好是 target,并返回它们的位置(索引) 。
规则:
- 只有一对正确答案
- 不能自己和自己配对
例子:
const nums = [1, 3, 2, 7, 6, 11, 8];
const target = 9; // 可能的答案:[0,6](1+8)、[1,4](3+6)、[2,3](2+7)
方法一:暴力枚举——“笨办法”
function twoSum(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]; // 找到答案,直接返回!
}
}
}
}
怎么做的:
- 两个循环,一个一个试所有可能的组合。
- 就像你拿着两个数,挨个问:“你们加起来等于
target吗?”
缺点:
- 空间:内存节省,但耗时。
适合场景:
方法二:indexOf 优化——“假装聪明”
javascript
function twoSum(nums, target) {
for (let i = 0; i < nums.length; i++) {
const complement = target - nums[i]; // 算出另一个数应该是多少
const index = nums.indexOf(complement); // 问数组:“有这个数吗?”
if (index !== -1 && index !== i) { // 不能是自己!
return [index, i]; // 找到答案,返回!
}
}
}
改进点:
- 先算出“另一个数”应该是多少,再问数组有没有。
- 代码看起来更“高级”。
问题:
-
indexOf本质还是遍历 → 时间复杂度还是 O(n2)。 - ⚠ 需要额外检查不能是自己(比如
index !== i)。
方法三:哈希表法——“开挂法”
function twoSum(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(1) 查询 → 问小本本“秒回”。
- 先查后存:避免自己和自己配对(防作弊)。
- 返回顺序:符合逻辑(先存后查)。
执行过程:
- 遍历到 1,算出需要 8, 记下 1 的位置。
- 遍历到 3,算出需要 6,记下 3 的位置。
- 遍历到 2,算出需要 7, 记下 2 的位置。
- 遍历到 7,算出需要 2 → 记录上上有,返回 [2, 3]。
优点:
- 时间:O(n) → 数据再多,耗时也很少。
- 空间:O(n) → 用空间换时间。
对比:哪种方法更快?
| 方法 | 时间复杂度 | 空间复杂度 | 性能表现(n=10⁴) |
|---|---|---|---|
| 暴力枚举 | O(n2) | O(1) | ≈1.3秒(慢!) |
| indexOf法 | O(n2) | O(1) | ≈0.9秒(还是慢!) |
| 哈希表 | O(n) | O(n) | ≈0.001秒(飞快!) |
```
javascript
const results = [];
if (map.has(complement)) {
results.push([map.get(complement), i]); // 收集所有答案
}
```
算法就像生活
两数之和问题告诉我们:用对方法,事半功倍。
哈希表就像“聪明人”,用空间换时间,生活更轻松!