【LeetCode】------ 两数之和:哈希表入门经典详解
导语
LeetCode 第 1 题"两数之和"(Two Sum)是无数人刷 LeetCode 的起点。这题看起来简单,但它是理解哈希表思想的绝佳入口。面试中这题基本不会直接考了,但它的变体------两数之和 II、三数之和、四数之和------会反复出现。把第 1 题吃透,后面那些题就是举一反三的事。
一、题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。
假设:每种输入只对应一个答案,且同一个元素不能使用两遍。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:nums[0] + nums[1] == 9
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
约束条件:
2 <= nums.length <= 10⁴-10⁹ <= nums[i] <= 10⁹-10⁹ <= target <= 10⁹- 只会存在一个有效答案
二、解法一:暴力枚举
最直觉的思路:两层循环,遍历所有两两组合,看哪一对加起来等于 target。
python
def twoSum(nums: list[int], target: int) -> list[int]:
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
if nums[i] + nums[j] == target:
return [i, j]
return []
复杂度:
- 时间:O(n²) --- 两层循环
- 空间:O(1)
这解法能过,但效率不高。面试官问"能不能优化",你就知道该往 O(n) 方向想了。
三、解法二:哈希表(推荐解法)
暴力的问题在于:对于每个 nums[i],我们都在后面遍历一遍去找 target - nums[i]。如果能 O(1) 时间查到那个数存不存在,就能把 O(n²) 降到 O(n)。
哈希表就是干这个的。
核心思路
遍历数组,对于每个元素 nums[i]:
- 计算
complement = target - nums[i] - 查哈希表里有没有
complement--- 有的话直接返回[hash[complement], i] - 没有的话,把
nums[i]存进哈希表,继续下一个
python
def twoSum(nums: list[int], target: int) -> list[int]:
hash_map = {} # 值 -> 下标
for i, num in enumerate(nums):
complement = target - num
if complement in hash_map:
return [hash_map[complement], i]
hash_map[num] = i
return []
图解过程
以 nums = [2, 7, 11, 15], target = 9 为例:
i=0, num=2, complement=7, 哈希表空 → 存入 {2:0}
i=1, num=7, complement=2, 哈希表有 2! → 返回 [0, 1]
两步就找到了。再看一个稍复杂的:
nums = [3, 2, 4], target = 6
i=0, num=3, complement=3, 哈希表空 → 存入 {3:0}
i=1, num=2, complement=4, 哈希表没有 4 → 存入 {3:0, 2:1}
i=2, num=4, complement=2, 哈希表有 2! → 返回 [1, 2]
代码实现(Java)
java
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
map.put(nums[i], i);
}
return new int[]{};
}
代码实现(Go)
go
func twoSum(nums []int, target int) []int {
hashMap := make(map[int]int)
for i, num := range nums {
complement := target - num
if j, ok := hashMap[complement]; ok {
return []int{j, i}
}
hashMap[num] = i
}
return nil
}
代码实现(C++)
cpp
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (map.count(complement)) {
return {map[complement], i};
}
map[nums[i]] = i;
}
return {};
}
复杂度:
- 时间:O(n) --- 只遍历一次
- 空间:O(n) --- 哈希表最多存 n 个元素
四、为什么是"先查后存"而不是"先存后查"
这是新手最容易写反的地方。
如果先把当前元素存进哈希表,再查 complement,会出现什么问题?
python
# 错误写法
for i, num in enumerate(nums):
hash_map[num] = i # 先存
complement = target - num
if complement in hash_map: # 后查
return [hash_map[complement], i]
当 nums = [3, 3], target = 6 时:
i=0, 存入 {3:0}, complement=3, 哈希表有 3! → 返回 [0, 0]
同一个元素用了两次,答案错了。
正确做法是先查再存,这样查到的一定是之前遍历过的元素,不会是自己。
五、两种解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力枚举 | O(n²) | O(1) | 数据量小、面试聊胜于无 |
| 哈希表 | O(n) | O(n) | 面试标准答案、数据量大 |
面试中直接写哈希表解法,暴力的提一嘴就行。
六、常见问题与避坑
1. 同一个元素不能用两次,怎么保证?
先查后存。查的时候哈希表里只有之前遍历过的元素,不可能查到自己。
2. 有重复元素怎么办?
比如 nums = [3, 3], target = 6。哈希表存的是"值 → 下标",第一个 3 存进去后下标是 0,第二个 3 来了,查到 complement 是 3,哈希表里有,返回 [0, 1]。没问题。
但要注意:如果同一元素出现多次,哈希表里只保留最后一次的下标。不过这题保证有且只有一个答案,所以无所谓。
3. 返回的是下标不是值
题目要求返回下标,不是返回元素本身。所以哈希表存的是 值 → 下标,不是 值 → 值。
4. 有序数组怎么办?
如果数组是有序的,可以用双指针(左右夹逼),时间 O(n),空间 O(1)。但 LeetCode 第 1 题的数组是无序的,而且题目要求返回原始下标,排序会丢掉下标信息。
LeetCode 第 167 题"两数之和 II"才是有序数组的版本,那题用双指针更合适。
5. 能不能用集合(Set)代替哈希表(Map)?
不行。集合只能判断"有没有",但我们需要知道 complement 的下标是多少。必须用 Map。
七、变体与延伸
两数之和 II(有序数组)
数组有序,用双指针:
python
def twoSum(numbers: list[int], target: int) -> list[int]:
left, right = 0, len(numbers) - 1
while left < right:
total = numbers[left] + numbers[right]
if total == target:
return [left + 1, right + 1] # 下标从 1 开始
elif total < target:
left += 1
else:
right -= 1
return []
三数之和
在两数之和的基础上多一层循环,先固定一个数,剩下两个用双指针找。注意去重。这是 LeetCode 第 15 题,值得单独写一篇。
四数之和
再套一层。时间 O(n³),但思路和三数之和一样。
八、总结
核心要点:
- 哈希表是解决"查找配对"问题的利器,把 O(n²) 降到 O(n)
- 记住"先查后存"的顺序,避免同一个元素被用两次
- 存的是
值 → 下标,因为题目要求返回下标 - 暴力解法能过但效率低,面试写哈希表版本
验证清单:
- 能手写哈希表解法,不看参考
- 理解"先查后存"的原因
- 能解释时间和空间复杂度
- 知道有序数组版本用双指针
参考资源
- LeetCode 第 1 题:https://leetcode.cn/problems/two-sum/
- LeetCode 第 167 题(两数之和 II):https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/
- LeetCode 第 15 题(三数之和):https://leetcode.cn/problems/3sum/