好的,我们这次来详细学习 LeetCode 第 1 题「两数之和」。
这是 LeetCode 的第一题,也是很多人入门算法与哈希表的经典题目。
1. 题目描述
题目链接:
https://leetcode.com/problems/two-sum/
题目:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用相同的元素两次。
可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
约束条件:
- 2 <= nums.length <= 10^4
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- 只会存在一个有效答案
2. 题意理解
我们要在数组里找两个不同的下标 i 和 j,使得:
\[\text{nums}[i] + \text{nums}[j] = \text{target} \]
并且同一个元素不能用两次(下标必须不同),但数组元素可能重复(如示例 3)。
3. 思路演进
3.1 暴力法(Brute Force)
最直接的想法是:
- 用两层循环,第一层
i从 0 到 n-1,第二层j从 i+1 到 n-1。 - 检查
nums[i] + nums[j] == target,如果成立,返回[i, j]。
时间复杂度:O(n²)
空间复杂度:O(1)
缺点:当 n 最大为 10^4 时,n² 是 10^8,在 LeetCode 上可能勉强通过,但效率低。
3.2 哈希表优化思路
我们想要避免内层循环,核心问题是:
对于当前数字 nums[i],我们要快速知道 target - nums[i] 是否在数组里出现过,并且知道它的下标。
方法:
用一个哈希表(字典/Map)来记录已经遍历过的数字及其下标。
- 遍历数组,对于每个元素
nums[i],计算complement = target - nums[i]。 - 检查
complement是否在哈希表中:- 如果在,说明前面已经出现过这个补数,直接返回
[map[complement], i]。 - 如果不在,把
nums[i]和下标i存入哈希表,继续遍历。
- 如果在,说明前面已经出现过这个补数,直接返回
为什么可行:
因为题目保证有且只有一组解,所以在找到解之前,补数一定没出现过,直到遇到配对的数时,补数已经在哈希表里。
4. 详细步骤举例
以示例 1 说明:nums = [2,7,11,15], target = 9
- 初始化哈希表
map = {} - i=0, nums[0]=2, complement=9-2=7
- 7 在 map 中吗?不在。
- 把 (2,0) 存入 map → map={2:0}
- i=1, nums[1]=7, complement=9-7=2
- 2 在 map 中吗?在,下标是 0。
- 返回 [0,1]
以示例 3 说明重复元素情况:nums = [3,3], target=6
- map={}
- i=0, nums[0]=3, complement=6-3=3
- 3 在 map 中吗?不在。
- 存 (3,0) → map={3:0}
- i=1, nums[1]=3, complement=6-3=3
- 3 在 map 中吗?在,下标是 0。
- 返回 [0,1]
- 注意这里虽然值相同,但下标不同,符合题意。
5. 代码实现(Python)
def twoSum(nums, target):
hashmap = {}
for i, num in enumerate(nums):
complement = target - num
if complement in hashmap:
return [hashmap[complement], i]
hashmap[num] = i
return [] # 题目保证有解,这里不会执行
时间复杂度:O(n),每个元素遍历一次,哈希表操作 O(1)。
空间复杂度:O(n),哈希表最多存储 n 个元素。
6. 关键点总结
- 利用哈希表将查找时间从 O(n) 降到 O(1),是“空间换时间”的典型例子。
- 一次遍历即可完成,因为我们在遍历时记录之前出现过的数字,并在后续判断补数是否存在。
- 重复元素不影响,因为我们在找到答案时,用的是之前出现的那个元素的下标,和当前下标,不会重复使用同一个元素。
这样讲解是否清晰?如果有哪部分希望我再展开或举例,请告诉我。