1. 两数之和
问题描述
给定一个整数数组 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]
算法思路
哈希表(字典)优化法
:
- 核心思想:
使用哈希表存储遍历过的数字及其索引,实现 O(1) 时间复杂度的差值查询。 - 遍历过程:
对于每个数字nums[i]
,计算补数complement = target - nums[i]
:- 若补数在哈希表中,直接返回结果
- 否则将当前数字存入哈希表
- 避免自用:
先查询后存储,确保不会使用同一个元素两次
代码实现
import java.util.HashMap;
import java.util.Map;
class Solution {
/**
* 查找两数之和等于目标值的索引
*
* @param nums 整数数组
* @param target 目标值
* @return 包含两个索引的数组
*/
public int[] twoSum(int[] nums, int target) {
// 创建哈希表:键-数字值,值-索引
Map<Integer, Integer> numMap = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
// 检查补数是否存在于哈希表中
if (numMap.containsKey(complement)) {
return new int[]{numMap.get(complement), i};
}
// 将当前数字存入哈希表
numMap.put(nums[i], i);
}
// 根据题目假设,此处不会执行
return new int[0];
}
}
代码注释
代码部分 | 说明 |
---|---|
Map<Integer, Integer> numMap | 哈希表:存储数字值与索引的映射关系 |
int complement = target - nums[i] | 计算当前数字所需的补数 |
numMap.containsKey(complement) | 检查补数是否在已遍历的数字中 |
new int[]{numMap.get(complement), i} | 找到结果时返回两个索引 |
numMap.put(nums[i], i) | 将当前数字存入哈希表(先查询后存储) |
算法过程
nums = [2,7,11,15]
, target = 9
:
- i=0:
nums[0]=2
→complement=7
哈希表为空 → 存入{2:0}
- i=1:
nums[1]=7
→complement=2
哈希表中有2
→ 返回[0,1]
复杂度分析
- 时间复杂度:O(n)
只需遍历数组一次,哈希表查询和插入均为 O(1) - 空间复杂度:O(n)
最坏情况下需要存储所有元素
关键点
- 哈希表优化查询:将查找时间从 O(n) 降为 O(1)
- 先查询后存储:避免同一个元素被错误匹配
- 一次遍历完成:同时进行查询和数据存储
- 结果唯一性:题目保证解唯一,找到即可返回
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 示例测试
int[] nums1 = {2,7,11,15};
int[] result1 = solution.twoSum(nums1, 9);
System.out.println("Test 1: " + Arrays.toString(result1)); // [0,1]
// 有负数的测试
int[] nums2 = {-3,4,3,90};
int[] result2 = solution.twoSum(nums2, 0);
System.out.println("Test 2: " + Arrays.toString(result2)); // [0,2]
// 重复元素测试
int[] nums3 = {3,2,4};
int[] result3 = solution.twoSum(nums3, 6);
System.out.println("Test 3: " + Arrays.toString(result3)); // [1,2]
// 相同元素不同位置
int[] nums4 = {3,3};
int[] result4 = solution.twoSum(nums4, 6);
System.out.println("Test 4: " + Arrays.toString(result4)); // [0,1]
}
常见问题
-
为什么用哈希表不用暴力法?
暴力法时间复杂度 O(n²),哈希表优化至 O(n),效率更高。 -
遇到重复元素怎么办?
哈希表存储最新索引,当出现重复元素时,新索引会覆盖旧索引。由于题目要求唯一解,且先查询后存储,不会影响结果(如[3,3]
,target=6
)。 -
哈希表存储什么内容?
存储数字值(键)和对应的索引(值),便于快速查询补数位置。 -
为什么先查询后存储?
避免同一个元素被匹配(如target=6
,当前元素为3时,若先存储会错误匹配自己)。
167. 两数之和 II - 输入有序数组
问题描述
给定一个已按非递减顺序排列的整数数组 numbers
,从数组中找出两个数,使它们的和等于目标值 target
。返回这两个数的下标(下标从 1 开始)。假设每个输入只对应唯一的答案,且不能重复使用相同元素。
示例:
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
算法思路
双指针法
:
- 指针初始化:
- 左指针
left
指向数组起始位置(下标 0) - 右指针
right
指向数组末尾位置(下标n-1
)
- 左指针
- 指针移动规则:
- 若
numbers[left] + numbers[right] > target
:右指针左移(减小总和) - 若
numbers[left] + numbers[right] < target
:左指针右移(增大总和) - 若相等:返回下标(注意下标从 1 开始)
- 若
- 终止条件:
left < right
(确保使用两个不同元素)
代码实现
class Solution {
public int[] twoSum(int[] numbers, int target) {
// 初始化双指针
int left = 0;
int right = numbers.length - 1;
while (left < right) {
int sum = numbers[left] + numbers[right];
if (sum > target) {
// 和过大,右指针左移
right--;
} else if (sum < target) {
// 和过小,左指针右移
left++;
} else {
// 找到目标,返回下标(从1开始)
return new int[]{left + 1, right + 1};
}
}
// 根据题目保证有解,此处不会执行
return new int[0];
}
}
代码注释
代码部分 | 说明 |
---|---|
int left = 0; | 左指针初始指向数组起始位置 |
int right = numbers.length - 1; | 右指针初始指向数组末尾位置 |
while (left < right) | 循环条件:确保使用两个不同元素 |
int sum = numbers[left] + numbers[right]; | 计算当前两数之和 |
sum > target → right-- | 和过大时,右指针左移(减小总和) |
sum < target → left++ | 和过小时,左指针右移(增大总和) |
return new int[]{left+1, right+1} | 找到目标时返回下标(从1开始) |
算法过程
numbers = [2,7,11,15]
, target = 9
:
- 初始状态:
left=0 → 2, right=3 → 15, sum=17 >9
- 右指针左移:
left=0 → 2, right=2 → 11, sum=13 >9
- 右指针再次左移:
left=0 → 2, right=1 → 7, sum=9 ==9
- 返回结果:
[1,2]
复杂度分析
- 时间复杂度:O(n)
最坏情况下遍历整个数组一次 - 空间复杂度:O(1)
仅使用常数空间
关键点
- 有序数组特性:利用非递减特性,通过指针移动高效调整总和
- 双指针移动规则:
- 总和过大 → 左移右指针
- 总和过小 → 右移左指针
- 下标转换:结果下标从 1 开始,返回时需
+1
- 唯一解保证:题目保证有唯一解,无需处理多解情况
测试用例
public static void main(String[] args) {
Solution solution = new Solution();
// 示例测试
int[] nums1 = {2,7,11,15};
int[] result1 = solution.twoSum(nums1, 9);
System.out.println("Test 1: " + Arrays.toString(result1)); // [1,2]
// 存在负数测试
int[] nums2 = {-5,-3,0,1,5};
int[] result2 = solution.twoSum(nums2, -2);
System.out.println("Test 2: " + Arrays.toString(result2)); // [2,4]
// 目标值较大测试
int[] nums3 = {1,2,3,4,5};
int[] result3 = solution.twoSum(nums3, 8);
System.out.println("Test 3: " + Arrays.toString(result3)); // [3,5]
// 最小长度数组
int[] nums4 = {1,2};
int[] result4 = solution.twoSum(nums4, 3);
System.out.println("Test 4: " + Arrays.toString(result4)); // [1,2]
}
常见问题
-
为什么双指针法有效?
数组有序性保证:- 右指针左移必然减小总和
- 左指针右移必然增大总和
通过系统性的指针移动,不会错过有效组合。
-
如何处理重复元素?
题目要求不能重复使用元素,但双指针天然使用不同位置的元素(left < right
),不会重复使用同一元素。 -
为什么返回下标从 1 开始?
题目要求,注意结果需转换为 1-based 索引。 -
无序数组能用此方法吗?
不能,必须先排序(但会破坏原始索引)。无序数组建议使用哈希表法。