LeetCode 1. 两数之和
题目描述
✅ 1. 两数之和

题意分析
给定整数数组
nums和目标值target,返回两个不同下标,使这两个下标上的数之和等于target。三个约束决定了解法边界。其一,返回的是下标而不是数值,所以任何打乱顺序的预处理都必须把原下标带上。其二,同一个元素不能用两次,这在
target = 2 * nums[i]时会成为真实陷阱。其三,数组无序且可能含重复值与负数,因此不能排序后直接二分,也不能靠数值去重。关键转化:两数之和的本质是配对。固定当前元素
nums[i]后,需要的另一个数被完全确定为need = target - nums[i],问题从「找两个数」降为「查一个数是否出现过」。查询用哈希表就是 $O(1)$。再加一个观察:任何一组答案
(i, j)且i < j,都会在扫描到j时被发现——因为那时i已经在表里了。所以只朝历史方向查询就不会漏解,不需要双层枚举。边界:题目保证恰好存在一个答案,所以不必处理无解;但代码结尾仍要有返回值以满足编译。
解法:一次遍历哈希表
核心思路
用哈希表记录已遍历元素的「值 → 下标」。遍历到
nums[i]时查找补数target - nums[i];命中后直接返回两个下标。必须先查再写,保证哈希表中只有当前元素之前的下标,不会重复使用同一个元素。
解题步骤
- 创建空哈希表
indexByValue。- 遍历数组,计算补数
need = target - nums[i]。- 若补数已在表中,返回补数下标和当前下标。
- 否则记录当前值及其下标,继续遍历。
代码实现
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> indexByValue = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
Integer j = indexByValue.get(need);
if (j != null) {
return new int[] {j, i};
}
indexByValue.put(nums[i], i);
}
return new int[0];
}
}
func twoSum(nums []int, target int) []int {
indexByValue := make(map[int]int)
for i, num := range nums {
need := target - num
if j, ok := indexByValue[need]; ok {
return []int{j, i}
}
indexByValue[num] = i
}
return []int{}
}
复杂度分析
- 时间复杂度:$O(n)$,每个元素至多查询、写入哈希表一次。
- 空间复杂度:$O(n)$,哈希表最多保存 $n$ 个元素。
关键点总结
- 哈希表保存下标,而不是只记录元素是否存在。
- 先查补数、后存当前值,既避免复用当前元素,也能正确处理重复值。
- 命中即返回,无需继续遍历或额外排序。
易错点总结
- 先存当前值再查补数,可能把同一元素使用两次。
- 哈希表只存元素、不存下标,无法构造返回值。
- 对重复值去重会漏掉
[3, 3]这类合法答案。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 167. 两数之和 II - 输入有序数组 | 中等 | 输入已有序,双指针可省掉排序做到 $O(1)$ 空间 |
| 170. 两数之和 III - 数据结构设计 | 简单 | 转成数据结构设计,权衡 add 与 find 的复杂度分配 |
| 653. 两数之和 IV - 输入二叉搜索树 | 简单 | 中序遍历得到有序序列,或遍历时用哈希集合查补数 |
| 1099. 小于 K 的两数之和 | 简单 | 由等于改成小于,双指针在移动时顺带维护最优解 |
| 15. 三数之和 | 中等 | 排序后固定一个数 + 内层双指针,额外处理去重 |
| 454. 四数相加 II | 中等 | 四数组分两半,用哈希表存前两组和的出现次数 |
| 888. 公平的糖果交换 | 简单 | 先由总和差推出目标差值,再退化为一次哈希查补数 |
| 面试题 16.24. 数对和 | 中等 | 要求返回全部数对,哈希需按数值计数而非命中即返回 |