文章目录
-
- 1.题目描述
-
- [1.1 示例](#1.1 示例)
- [1.2 提示](#1.2 提示)
- [1.3 进阶](#1.3 进阶)
- [2. 解法分析](#2. 解法分析)
-
- [2.1 方法一:暴力枚举(双重循环)](#2.1 方法一:暴力枚举(双重循环))
- [2.2 方法二:哈希表(先存后找)](#2.2 方法二:哈希表(先存后找))
- [2.3 方法三:哈希表(边存边找,最优解)](#2.3 方法三:哈希表(边存边找,最优解))
- [3. 三种方法对比](#3. 三种方法对比)
- [4. 总结与感悟](#4. 总结与感悟)
原题链接:1. 两数之和
1.题目描述
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。你可以按任意顺序返回答案。
1.1 示例
示例 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]
1.2 提示
2 <= nums.length <= 10^4-10^9 <= nums[i] <= 10^9-10^9 <= target <= 10^9- 只会存在一个有效答案
1.3 进阶
你可以想出一个时间复杂度小于 O(n^2) 的算法吗?
2. 解法分析
2.1 方法一:暴力枚举(双重循环)
思路
最直观的方法:遍历数组中的每一个元素 nums[i],再遍历它之后的所有元素 nums[j],判断两数之和是否等于 target。
代码实现
java
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
int length = nums.length;
for (int i = 0; i < length; i++) {
// 从 i+1 开始,避免使用同一个元素两次
for (int j = i + 1; j < length; j++) {
if (nums[i] + nums[j] == target) {
result[0] = i;
result[1] = j;
return result; // 题目保证只有一个有效答案,直接返回
}
}
}
return result;
}
复杂度分析
| 复杂度 | 量级 | 说明 |
|---|---|---|
| 时间复杂度 | O(n²) | 两层循环,最坏情况下每个元素都会被遍历 n 次 |
| 空间复杂度 | O(1) | 只使用了固定大小的额外空间(result 数组和循环变量) |
优点:简单直接,不需要额外数据结构。
缺点:当数组长度很大(如 n = 10^4)时,O(n²) 会非常慢(约 1 亿次操作)。
2.2 方法二:哈希表(先存后找)
思路
为了降低查找 "target - nums[i]" 的时间,我们可以使用哈希表(HashMap)存储每个元素的值和它的下标。
第一步:遍历数组,将所有元素存入哈希表。
第二步:再次遍历数组,对于每个 nums[i],检查 target - nums[i] 是否在哈希表中,并且下标不能与 i 相同。
代码实现
java
public int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
// 第一遍:存入所有元素及其下标
for (int i = 0; i < nums.length; i++) {
map.put(nums[i], i);
}
// 第二遍:查找符合条件的另一个数
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (map.containsKey(complement)) {
int index = map.get(complement);
if (i != index) { // 不能是同一个元素
return new int[]{i, index};
}
}
}
return new int[0];
}
复杂度分析
| 复杂度 | 量级 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 两个循环各 O(n),哈希表操作平均 O(1),整体 O(n) |
| 空间复杂度 | O(n) | 哈希表最多存储 n 个键值对 |
优点:时间复杂度降为 O(n),适合数据量大的场景。
缺点:需要额外的 O(n) 空间。
2.3 方法三:哈希表(边存边找,最优解)
思路
其实不需要两次循环。我们可以在遍历数组的同时,将当前元素需要的 "互补数" 在哈希表中查找,如果找到了就直接返回;如果没找到,就把当前元素存入哈希表,供后面的元素匹配。
这样一次遍历即可完成,而且天然避免了使用同一个元素两次(因为查找时当前元素还未存入哈希表)。
代码实现
java
public int[] twoSum3(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[0];
}
复杂度分析
| 复杂度 | 量级 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 一次循环,哈希表操作 O(1) |
| 空间复杂度 | O(n) | 最坏情况下哈希表存储 n-1 个元素 |
优点:时间最优(只需一次遍历),代码简洁优雅。
注意:虽然空间复杂度仍是 O(n),但实际存储的元素数量可能略少于方法二(最坏情况相同)。
3. 三种方法对比
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| 暴力枚举 | O(n²) | O(1) | 数组长度很小(如 n ≤ 1000) |
| 哈希表(先存后找) | O(n) | O(n) | 通用,但多一次循环 |
| 哈希表(边存边找) | O(n) | O(n) | 推荐,代码最简洁,效率最高 |
4. 总结与感悟
通过这道题,我们可以得到几点重要的收获:
-
用空间换时间是常见优化手段
暴力法不需要额外空间,但时间慢。引入哈希表后,时间复杂度从 O(n²) 降到 O(n),虽然多用了 O(n) 的空间,但对于现代计算机来说通常是值得的。
-
哈希表的查找效率极高
在需要频繁查找 "某个元素是否存在" 或 "获取某个元素对应的值" 时,哈希表的平均 O(1) 时间复杂度几乎是不可替代的。
-
一次遍历的技巧
"边存边找" 的方法非常巧妙,它利用了哈希表存储的是已经遍历过的元素这一特性,避免了使用同一个元素两次,同时减少了一次循环。这种 "延迟存入" 的思路在很多题目中都可以复用。
-
注意避免的坑
- 不能使用同一个元素两次 → 在边存边找中,当前元素还没有存入,自然就不会匹配到自己。
- 题目保证只有一个有效答案 → 所以找到后可以立即返回,不需要继续查找。
希望这篇博客对你有所帮助,也欢迎大家在评论区交流讨论!如果觉得有用,不妨点个赞支持一下 !感谢,共勉,祝好!😊