大家好!今天开始我们正式开启 LeetCode 热题 100 系列的讲解,第一题就是经典的 "两数之和"。这道题不仅是 LeetCode 的入门必刷题,更是面试中高频出现的基础题,掌握它的解题思路,能帮我们建立对数组类问题的基本认知。接下来,我们就从题目分析、解法推导到代码实现,一步步把这道题吃透。
一、题目描述
首先,我们先明确题目要求(基于 LeetCode 官方原题):
- 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
- 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
- 你可以按任意顺序返回答案。
示例
比如输入:nums = [2,7,11,15],target = 9
因为 2 + 7 = 9,所以返回 [0,1](数组下标从 0 开始)。
提示
- 2 <= nums.length <= 10⁴(数组长度至少为 2,保证有解)
- -10⁹ <= nums[i] <= 10⁹
- -10⁹ <= target <= 10⁹
- 只会存在一个有效答案
二、解题思路分析
解决这道题,我们可以从 "暴力解法" 入手,再逐步优化,理解不同解法的时间复杂度差异,这也是面试中面试官希望看到的思考过程。
1. 暴力解法:双重循环(入门思路)
思路逻辑
最直观的想法是:遍历数组中的每一个元素,对于当前元素 nums[i],再遍历它后面的所有元素 nums[j](j > i),判断 nums[i] + nums[j] 是否等于 target。如果等于,直接返回 [i, j]即可。
代码实现
java
public class No1 {
public static void main(String[] args) {
int[] nums = {2,7,11,15};
int target = 13;
int[] result = Solution.twoSum(nums, target);
System.out.println(Arrays.toString(result));
}
public static class Solution {
public static int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < nums.length; j++) {
if(target == nums[i]+nums[j]){
return new int[]{i,j};
}
}
}
return null;
}
}
}
复杂度分析
- 时间复杂度:O (n²)。外层循环执行 n 次,内层循环平均执行 n/2 次,整体是二次方级别的操作,当数组长度很大(比如接近 10⁴)时,效率会很低。
- 空间复杂度:O (1)。没有额外使用与数组长度相关的存储空间,只用到了几个变量。
缺点
暴力解法虽然思路简单,但时间复杂度太高,在面试中通常不会被接受,我们需要更高效的方法。
2. 优化解法:哈希表(推荐思路)
核心思路:用空间换时间
暴力解法的问题在于 "查找互补元素" 的过程太慢(每次都要遍历后面的元素)。如果我们能把 "查找互补元素" 的时间从 O (n) 降到 O (1),整体时间复杂度就能降到 O (n)。
这里的 "互补元素" 指的是:对于当前元素 nums[i],能和它组成 target 的元素是 complement = target - nums[i]。我们需要快速判断 complement 是否在数组中,并且获取它的下标。
而哈希表(HashMap) 正好可以实现 "键值对" 的快速查找:
- 把数组中的元素作为 "键(key)",元素的下标作为 "值(value)" 存入哈希表。
- 遍历数组时,对于当前元素 nums[i],计算 complement = target - nums[i]。
- 检查哈希表中是否存在 complement 这个键:
- 如果存在,说明之前已经遍历过这个互补元素,直接返回 [哈希表中complement对应的值, i]。
- 如果不存在,就把当前元素 nums[i] 和它的下标 i 存入哈希表,继续遍历下一个元素。
为什么能保证不重复?
因为我们是 "边遍历边存表",每次查找的都是已经遍历过的元素(即下标小于当前 i 的元素),所以不会出现同一个元素被重复使用的情况。
代码实现(Java 版)
java
public class No1point1 {
public static void main(String[] args) {
int[] nums = {2, 7, 9, 11};
int target = 11;
int[] result = No1.Solution.twoSum(nums, target);
System.out.println(Arrays.toString(result));
}
public int[] twoSum(int[] nums, int target) {
// 初始化哈希表,key存数组元素,value存元素下标
Map<Integer, Integer> map = new HashMap<>();
int n = nums.length;
// 遍历数组
for (int i = 0; i < n; i++) {
// 计算互补元素
int complement = target - nums[i];
// 检查互补元素是否在哈希表中
if (map.containsKey(complement)) {
// 存在则返回结果(互补元素的下标在前,当前下标在后)
return new int[]{map.get(complement), i};
}
// 不存在则将当前元素和下标存入哈希表
map.put(nums[i], i);
}
return null;
}
}
复杂度分析
- 时间复杂度:O (n)。只遍历了一次数组,每次哈希表的查找和插入操作都是 O (1),整体是线性时间。
- 空间复杂度:O (n)。最坏情况下,需要把数组中所有元素都存入哈希表,存储空间与数组长度成正比。
优点
用少量的空间代价,换来了时间复杂度的大幅降低,是面试中的最优解,也是实际工程中推荐使用的方法。
三、测试案例验证
为了确保代码的正确性,我们可以用题目中的示例和一些边缘案例进行测试。
测试案例 1:基础案例
输入:nums = [2,7,11,15],target = 9
输出:[0,1]

测试案例 2:元素为负数
输入:nums = [3,2,4],target = 6
输出:[1,2]

测试案例 3:元素顺序不同
输入:nums = [3,3],target = 6
输出:[0,1]

四、总结
"两数之和" 虽然是一道简单题,但它涵盖了两个重要的解题思路:
- 暴力解法:适合入门理解问题,但效率低,不推荐实际使用。
- 哈希表解法:用空间换时间,是最优解,需要掌握哈希表的 "快速查找" 特性。
这道题的核心启示是:当遇到 "需要快速查找某个元素是否存在" 的场景时,优先考虑哈希表这种数据结构,它能极大提升查找效率。
下一篇我们会讲解 LeetCode 热题 100 的第二题,感兴趣的同学可以持续关注~如果有疑问,欢迎在评论区留言讨论!