作为 LeetCode 的第一道入门题,「两数之和」虽然标注为简单 难度,却是理解数组遍历与哈希表核心思想的经典案例。它不仅是算法新手的入门必修课,更能让我们体会到 空间换时间 的优化思维。今天从最基础的暴力解法开始,一步步拆解到时间复杂度 O (n) 的最优解法,带你彻底搞懂这道题的解题逻辑~
📖 题目重述
给你一个整数数组 nums 和一个目标值 target,请在数组中找出两个不同位置 的整数,使它们的和恰好等于 target,最后返回这两个数的下标。这里有几个关键规则要注意:
- 每个输入都只有一个正确答案;
- 数组中的同一个元素不能被重复使用;
- 返回的下标顺序没有要求。
举个例子,输入数组 [2,7,11,15]、目标值 9,因为 2+7=9,所以返回下标 [0,1] 即可。
🚶 阶梯思路拆解
第一步:暴力枚举所有可能 🥾
要找两个数的和等于目标值,最直观的想法就是把数组中所有数两两配对,逐一检查和是否符合要求。这就是暴力解法的核心逻辑,也是最容易想到的思路。
💡 核心逻辑
用两层嵌套的 for 循环遍历数组:
- 外层循环固定一个数
nums[i],遍历范围是数组的每一个元素; - 内层循环遍历
i之后的所有数nums[j](避免重复检查同一对数字); - 若
nums[i] + nums[j] == target,直接返回下标数组{i, j}。
✅ 代码实现(Java)
ini
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
// 外层循环固定第一个数
for (int i = 0; i < n; i++) {
// 内层循环遍历第二个数,从i+1开始避免重复
for (int j = i + 1; j < n; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
// 题目保证有解,此处仅为语法兜底
return new int[0];
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums1 = {2,7,11,15};
int[] res1 = solution.twoSum(nums1, 9);
System.out.println(res1[0] + "," + res1[1]); // 输出0,1
int[] nums2 = {3,2,4};
int[] res2 = solution.twoSum(nums2, 6);
System.out.println(res2[0] + "," + res2[1]); // 输出1,2
}
}
⚙️ 复杂度分析
| 复杂度类型 | 计算结果 | 说明 |
|---|---|---|
| 时间复杂度 | O(n²) | 两层嵌套循环,最坏情况下需要遍历 n*(n-1)/2 次,n 为数组长度 |
| 空间复杂度 | O(1) | 仅使用了几个临时变量,未开辟额外的数组或集合空间 |
🚫 遇到的问题
暴力解法的逻辑简单,但效率极低。当数组长度 n 达到 10⁴ 时,n² 就是 10⁸ 次运算,会触发 LeetCode 的超时判定;如果是百万级别的数组,这种方法更是完全不可行。问题的核心在于:内层循环的查找过程重复且耗时,我们需要找到一种更高效的查找方式。
第二步:用哈希表优化查找效率 🗺️
既然暴力解法的痛点是 查找补数 的过程太慢,那我们可以用 哈希表(HashMap)来记录已经遍历过的数字和它的下标,把查找操作从 O (n) 降到 O (1)。这是算法优化中典型的 空间换时间 策略。
💡 核心逻辑
- 初始化一个
HashMap,键存储遍历过的数字 ,值存储该数字对应的数组下标; - 遍历数组时,对当前数字
nums[i],计算它的补数complement = target - nums[i]; - 检查补数是否存在于 HashMap 中:
- 若存在,说明之前已经遍历过这个补数,直接返回
{map.get(complement), i}; - 若不存在,将当前数字和下标存入 HashMap,继续遍历下一个元素。
- 若存在,说明之前已经遍历过这个补数,直接返回
📊 图文演示(以 nums=[2,7,11,15], target=9 为例)
我们一步步拆解 HashMap 的变化和查找过程,直观感受优化后的逻辑(如图所示):
-
遍历 i=0,nums [i]=2,补数 = 9-2=7。此时 HashMap 为空,将
2→0存入 Map:iniHashMap:{2=0} 当前遍历:nums[0]=2 → 补数7不存在 ❌ -
遍历 i=1,nums [i]=7,补数 = 9-7=2。检查 HashMap,发现 2 存在且对应下标 0:
iniHashMap:{2=0} 当前遍历:nums[1]=7 → 补数2存在 ✅ → 返回{0,1}
✅ 代码实现(Java)
ini
import java.util.HashMap;
import java.util.Map;
class Solution {
public int[] twoSum(int[] nums, int target) {
// 初始化HashMap,键:数字,值:下标
Map<Integer, Integer> numMap = new HashMap<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
int complement = target - nums[i];
// 检查补数是否在HashMap中
if (numMap.containsKey(complement)) {
return new int[]{numMap.get(complement), i};
}
// 若不存在,将当前数字和下标存入Map
numMap.put(nums[i], i);
}
// 题目保证有解,此处仅为语法兜底
return new int[0];
}
}
// 测试代码
public class Main {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums1 = {2,7,11,15};
int[] res1 = solution.twoSum(nums1, 9);
System.out.println(res1[0] + "," + res1[1]); // 输出0,1
int[] nums2 = {3,3};
int[] res2 = solution.twoSum(nums2, 6);
System.out.println(res2[0] + "," + res2[1]); // 输出0,1
}
}
⚙️ 复杂度分析
| 复杂度类型 | 计算结果 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 仅需遍历一次数组,HashMap 的查找和插入操作均为 O (1) |
| 空间复杂度 | O(n) | 最坏情况下,需要将数组中 n 个元素全部存入 HashMap |
✨ 优化亮点
这种方法通过 HashMap 将原本 O (n) 的查找过程优化为 O (1),时间复杂度从 O (n²) 骤降到 O (n),虽然牺牲了部分空间,但在实际开发中,时间效率的提升往往更重要。这也是「两数之和」这道题的最优解法。
📝 总结
「两数之和」看似简单,却蕴含了算法优化的核心思维:用合适的数据结构解决性能瓶颈 。我们从暴力枚举的基础思路出发,找到 重复查找 的性能问题,再通过哈希表实现高效查找,这个优化路径也是很多数组类算法题的通用解题模板。
同类题扩展建议
掌握了这道题的哈希表思路后,可以继续练习这些进阶题目,巩固优化思维:
- LeetCode 15. 三数之和:在两数之和的基础上增加一层循环,结合双指针优化去重;
- LeetCode 18. 四数之和:延续三数之和的思路,注意边界条件和去重逻辑;
- LeetCode 454. 四数相加 II:用两个 HashMap 分别存储两组数的和,进一步降低时间复杂度。
算法学习不是一蹴而就的,从基础解法开始逐步优化,才能真正理解背后的逻辑。希望这篇文章能帮你吃透「两数之和」,也为后续的算法学习打下坚实的基础~
📢 关注不迷路,算法学习更高效!
如果这篇拆解对你有帮助,别忘了关注我的微信公众号【小镇冥想人】 ~ 后续会持续更新 LeetCode 高频题的阶梯式解题思路,从暴力到最优解层层递进,每道题都搭配详细图文和代码注释,帮你轻松攻克算法难关。