LeetCode100天Day11-两数之和与合并两个有序数组:暴力搜索与双指针合并
摘要:本文详细解析了LeetCode中两道经典数组题目------"两数之和"和"合并两个有序数组"。通过暴力查找解决两数之和问题,以及使用双指针合并有序数组,帮助读者掌握数组操作的基础技巧。
目录
文章目录
- LeetCode100天Day11-两数之和与合并两个有序数组:暴力搜索与双指针合并
-
- 目录
- [1. 两数之和(Two Sum)](#1. 两数之和(Two Sum))
-
- [1.1 题目描述](#1.1 题目描述)
- [1.2 解题思路](#1.2 解题思路)
- [1.3 代码实现](#1.3 代码实现)
- [1.4 代码逐行解释](#1.4 代码逐行解释)
- [1.5 执行流程详解](#1.5 执行流程详解)
- [1.6 算法图解](#1.6 算法图解)
- [1.7 复杂度分析](#1.7 复杂度分析)
- [1.8 边界情况](#1.8 边界情况)
- [2. 合并两个有序数组(Merge Sorted Array)](#2. 合并两个有序数组(Merge Sorted Array))
-
- [2.1 题目描述](#2.1 题目描述)
- [2.2 解题思路](#2.2 解题思路)
- [2.3 代码实现](#2.3 代码实现)
- [2.4 代码逐行解释](#2.4 代码逐行解释)
- [2.5 执行流程详解](#2.5 执行流程详解)
- [2.6 算法图解](#2.6 算法图解)
- [2.7 复杂度分析](#2.7 复杂度分析)
- [2.8 边界情况](#2.8 边界情况)
- [3. 两题对比与总结](#3. 两题对比与总结)
-
- [3.1 算法对比](#3.1 算法对比)
- [3.2 双指针模板](#3.2 双指针模板)
- [3.3 暴力枚举 vs 双指针](#3.3 暴力枚举 vs 双指针)
- [3.4 数组克隆的注意事项](#3.4 数组克隆的注意事项)
- [4. 总结](#4. 总结)
- 参考资源
- 文章标签
1. 两数之和(Two Sum)
1.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]
1.2 解题思路
这道题使用暴力枚举的方法:
- 使用两层循环遍历数组
- 外层循环固定第一个数
- 内层循环寻找第二个数
- 找到满足条件的两个数后返回下标
解题步骤:
- 创建长度为2的结果数组
- 外层循环遍历数组,索引为i
- 内层循环从i+1开始,索引为j
- 检查nums[i] + nums[j]是否等于target
- 如果相等,保存下标并返回
1.3 代码实现
java
class Solution {
public int[] twoSum(int[] nums, int target) {
int answer[] = new int[2];
for(int i = 0;i < nums.length;i++){
for(int j = i+1;j < nums.length;j++){
if(nums[i] + nums[j] == target){
answer[0] = i;
answer[1] = j;
return answer;
}
}
}
return answer;
}
}
1.4 代码逐行解释
第一部分:创建结果数组
java
int answer[] = new int[2];
功能:
- 创建长度为2的数组,用于存储答案
answer[0]存储第一个数的下标answer[1]存储第二个数的下标
第二部分:双层循环
java
for(int i = 0;i < nums.length;i++){
for(int j = i+1;j < nums.length;j++){
if(nums[i] + nums[j] == target){
answer[0] = i;
answer[1] = j;
return answer;
}
}
}
循环结构:
| 循环 | 变量 | 起始值 | 作用 |
|---|---|---|---|
| 外层 | i | 0 | 固定第一个数 |
| 内层 | j | i+1 | 寻找第二个数 |
为什么j从i+1开始:
java
// 避免重复检查
// 例如:nums = [2, 7, 11, 15], target = 9
// 如果j从0开始:
i=0, j=0: nums[0]+nums[0] = 2+2 = 4 (同一个元素,不符合题意)
i=0, j=1: nums[0]+nums[1] = 2+7 = 9 ✓
i=1, j=0: nums[1]+nums[0] = 7+2 = 9 (重复检查)
i=1, j=1: nums[1]+nums[1] = 7+7 = 14 (同一个元素)
...
// j从i+1开始:
i=0, j=1: nums[0]+nums[1] = 2+7 = 9 ✓ (找到答案)
i=0, j=2: nums[0]+nums[2] = 2+11 = 13
i=0, j=3: nums[0]+nums[3] = 2+15 = 17
i=1, j=2: nums[1]+nums[2] = 7+11 = 18
...
1.5 执行流程详解
示例1 :nums = [2,7,11,15], target = 9
初始状态:
answer = [0, 0]
nums = [2, 7, 11, 15]
target = 9
i=0, nums[i]=2:
j=1, nums[j]=7:
nums[0] + nums[1] = 2 + 7 = 9
9 == 9? 是,找到答案
answer[0] = 0
answer[1] = 1
返回 [0, 1]
输出: [0, 1]
示例2 :nums = [3,2,4], target = 6
i=0, nums[i]=3:
j=1, nums[j]=2:
3 + 2 = 5 != 6
j=2, nums[j]=4:
3 + 4 = 7 != 6
i=1, nums[i]=2:
j=2, nums[j]=4:
2 + 4 = 6 == 6? 是,找到答案
answer[0] = 1
answer[1] = 2
返回 [1, 2]
输出: [1, 2]
示例3 :nums = [3,3], target = 6
i=0, nums[i]=3:
j=1, nums[j]=3:
nums[0] + nums[1] = 3 + 3 = 6
6 == 6? 是,找到答案
answer[0] = 0
answer[1] = 1
返回 [0, 1]
输出: [0, 1]
1.6 算法图解
nums = [2, 7, 11, 15]
target = 9
步骤1: i=0, 固定nums[0]=2
数组: [2, 7, 11, 15]
索引: ↑
i=0
检查: nums[0] + nums[1] = 2 + 7 = 9 ✓
步骤2: j从i+1=1开始
数组: [2, 7, 11, 15]
索引: ↑ ↑
i=0 j=1
2 + 7 = 9 == target ✓
结果: [0, 1]
nums = [3, 2, 4]
target = 6
步骤1: i=0, nums[0]=3
数组: [3, 2, 4]
索引: ↑ ↑ ↑
i=0 j=1 j=2
j=1: 3 + 2 = 5 != 6
j=2: 3 + 4 = 7 != 6
步骤2: i=1, nums[1]=2
数组: [3, 2, 4]
索引: ↑ ↑
i=1 j=2
j=2: 2 + 4 = 6 == 6 ✓
结果: [1, 2]
1.7 复杂度分析
| 分析维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n²) | 两层循环 |
| 空间复杂度 | O(1) | 只使用常数空间 |
优化思路:可以使用HashMap将时间复杂度降到O(n)
java
// 优化版本:使用HashMap
class Solution {
public int[] twoSum(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[]{};
}
}
1.8 边界情况
| nums | target | 说明 | 输出 |
|---|---|---|---|
[2,7] |
9 |
最小数组 | [0,1] |
[3,3] |
6 |
相同元素 | [0,1] |
[3,2,4] |
6 |
不相邻 | [1,2] |
2. 合并两个有序数组(Merge Sorted Array)
2.1 题目描述
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中 ,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0,应忽略。nums2 的长度为 n。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6],合并结果是 [1,2,2,3,5,6]
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
2.2 解题思路
这道题使用双指针合并的方法:
- 克隆nums1作为备份
- 使用双指针分别遍历nums3和nums2
- 比较两个指针指向的元素,将较小的放入nums1
- 处理剩余元素
解题步骤:
- 克隆nums1到nums3作为备份
- 创建三个指针:index(结果位置), index1(nums3), index2(nums2)
- 比较nums3[index1]和nums2[index2],小的放入nums1[index]
- 移动相应指针,直到一个数组遍历完
- 处理剩余元素
2.3 代码实现
java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int index = 0;
int[] nums3 = nums1.clone();
int index1 = 0;
int index2 = 0;
while (index1 != n && index2 != m) {
if (nums3[index2] > nums2[index1]) {
nums1[index] = nums2[index1];
index++;
index1++;
} else {
nums1[index] = nums3[index2];
index++;
index2++;
}
}
for (; index < m + n && index1 < n; index++) {
nums1[index] = nums2[index1];
index1++;
}
for (; index < m + n; index++) {
nums1[index] = nums3[index2];
index2++;
}
}
}
2.4 代码逐行解释
第一部分:初始化
java
int index = 0;
int[] nums3 = nums1.clone();
int index1 = 0;
int index2 = 0;
变量说明:
| 变量 | 作用 | 指向 |
|---|---|---|
index |
nums1的写入位置 | 从0开始 |
nums3 |
nums1的备份 | nums1的前m个元素 |
index1 |
nums2的读取位置 | 从0开始 |
index2 |
nums3的读取位置 | 从0开始 |
为什么要克隆nums1:
java
nums1 = [1, 2, 3, 0, 0, 0]
m = 3, n = 3
nums2 = [2, 5, 6]
// 如果直接使用nums1:
// nums1[0] = min(nums1[0], nums2[0])
// 但nums1[0]会被覆盖,原值丢失
// 克隆后:
nums3 = [1, 2, 3]
// nums3保留原值,nums1可以安全修改
第二部分:双指针合并
java
while (index1 != n && index2 != m) {
if (nums3[index2] > nums2[index1]) {
nums1[index] = nums2[index1];
index++;
index1++;
} else {
nums1[index] = nums3[index2];
index++;
index2++;
}
}
合并逻辑:
| 条件 | 操作 | 说明 |
|---|---|---|
nums3[index2] > nums2[index1] |
取nums2的元素 | nums2的更小 |
| 否则 | 取nums3的元素 | nums3的更小或相等 |
注意 :条件中有个bug,应该是 index1 < n 而不是 index1 != n,但逻辑上两者等价(因为index1只增不减)
第三部分:处理剩余元素
java
for (; index < m + n && index1 < n; index++) {
nums1[index] = nums2[index1];
index1++;
}
for (; index < m + n; index++) {
nums1[index] = nums3[index2];
index2++;
}
处理逻辑:
| 循环 | 条件 | 操作 |
|---|---|---|
| 第一个 | index1 < n |
nums2还有剩余元素 |
| 第二个 | index < m + n |
nums3还有剩余元素 |
2.5 执行流程详解
示例1 :nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
初始状态:
nums1 = [1, 2, 3, 0, 0, 0]
nums2 = [2, 5, 6]
nums3 = [1, 2, 3]
index = 0, index1 = 0, index2 = 0
步骤1: index=0, index1=0, index2=0
nums3[0]=1, nums2[0]=2
1 > 2? 否
nums1[0] = nums3[0] = 1
index = 1, index2 = 1
nums1 = [1, 2, 3, 0, 0, 0]
步骤2: index=1, index1=0, index2=1
nums3[1]=2, nums2[0]=2
2 > 2? 否
nums1[1] = nums3[1] = 2
index = 2, index2 = 2
nums1 = [1, 2, 3, 0, 0, 0]
步骤3: index=2, index1=0, index2=2
nums3[2]=3, nums2[0]=2
3 > 2? 是
nums1[2] = nums2[0] = 2
index = 3, index1 = 1
nums1 = [1, 2, 2, 0, 0, 0]
步骤4: index=3, index1=1, index2=2
nums3[2]=3, nums2[1]=5
3 > 5? 否
nums1[3] = nums3[2] = 3
index = 4, index2 = 3
index2 == m,退出循环
处理剩余:
index1=1 < 3, nums2还有元素
nums1[4] = nums2[1] = 5
index1=2, index=5
nums1[5] = nums2[2] = 6
index1=3, index=6
最终输出: [1, 2, 2, 3, 5, 6]
示例2 :nums1 = [1], m = 1, nums2 = [], n = 0
初始状态:
nums1 = [1]
nums2 = []
nums3 = [1]
index = 0, index1 = 0, index2 = 0
while循环:index1=0, index2=0
index1 != n(0)? 否,不进入循环
处理剩余:
index1 < n? 0 < 0? 否
index < m + n? 0 < 1? 是
nums1[0] = nums3[0] = 1
最终输出: [1]
示例3 :nums1 = [0], m = 0, nums2 = [1], n = 1
初始状态:
nums1 = [0]
nums2 = [1]
nums3 = [0] (但m=0,不使用)
index = 0, index1 = 0, index2 = 0
while循环:index1=0, index2=0
index2 != m(0)? 否,不进入循环
处理剩余:
index1 < n? 0 < 1? 是
nums1[0] = nums2[0] = 1
index1 = 1, index = 1
最终输出: [1]
2.6 算法图解
nums1 = [1, 2, 3, 0, 0, 0]
nums2 = [2, 5, 6]
步骤1:
nums3: [1, 2, 3]
↑
index2=0
nums2: [2, 5, 6]
↑
index1=0
比较: 1 vs 2
取1,放入nums1[0]
nums1: [1, 2, 3, 0, 0, 0]
↑
index=0→1
步骤2:
nums3: [1, 2, 3]
↑
index2=1
nums2: [2, 5, 6]
↑
index1=0
比较: 2 vs 2
取nums3的2,放入nums1[1]
nums1: [1, 2, 3, 0, 0, 0]
↑
index=1→2
步骤3:
nums3: [1, 2, 3]
↑
index2=2
nums2: [2, 5, 6]
↑
index1=0
比较: 3 vs 2
取nums2的2,放入nums1[2]
nums1: [1, 2, 2, 0, 0, 0]
↑
index=2→3
继续...直到合并完成
最终结果:
nums1: [1, 2, 2, 3, 5, 6]
2.7 复杂度分析
| 分析维度 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(m + n) | 遍历两个数组 |
| 空间复杂度 | O(m) | 克隆nums1 |
优化思路:可以从后向前合并,避免克隆
java
// 优化版本:从后向前
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1;
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] > nums2[p2]) {
nums1[p--] = nums1[p1--];
} else {
nums1[p--] = nums2[p2--];
}
}
while (p2 >= 0) {
nums1[p--] = nums2[p2--];
}
}
}
2.8 边界情况
| nums1 | m | nums2 | n | 说明 | 输出 |
|---|---|---|---|---|---|
[1] |
1 | [] |
0 | nums2为空 | [1] |
[0] |
0 | [1] |
1 | nums1为空 | [1] |
[1,2,3,0,0,0] |
3 | [2,5,6] |
3 | 正常情况 | [1,2,2,3,5,6] |
3. 两题对比与总结
3.1 算法对比
| 对比项 | 两数之和 | 合并两个有序数组 |
|---|---|---|
| 核心算法 | 暴力枚举 | 双指针合并 |
| 数据结构 | 数组 | 数组 |
| 时间复杂度 | O(n²) | O(m + n) |
| 空间复杂度 | O(1) | O(m) |
| 应用场景 | 查找问题 | 合并问题 |
3.2 双指针模板
java
// 双指针合并模板
int i = 0; // 数组1的指针
int j = 0; // 数组2的指针
int k = 0; // 结果数组的指针
while (i < len1 && j < len2) {
if (array1[i] < array2[j]) {
result[k++] = array1[i++];
} else {
result[k++] = array2[j++];
}
}
// 处理剩余元素
while (i < len1) {
result[k++] = array1[i++];
}
while (j < len2) {
result[k++] = array2[j++];
}
3.3 暴力枚举 vs 双指针
| 对比项 | 暴力枚举 | 双指针 |
|---|---|---|
| 时间复杂度 | O(n²) | O(n) |
| 适用场景 | 无序数组 | 有序数组 |
| 实现难度 | 简单 | 中等 |
| 空间效率 | 高 | 中 |
3.4 数组克隆的注意事项
java
// 浅克隆 vs 深克隆
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1.clone(); // 浅克隆,对于基本类型足够
int[][] arr3 = {{1, 2}, {3, 4}};
int[][] arr4 = arr3.clone(); // 浅克隆,子数组共享引用
// 对于二维数组,需要深克隆
int[][] arr5 = new int[arr3.length][];
for (int i = 0; i < arr3.length; i++) {
arr5[i] = arr3[i].clone();
}
4. 总结
今天我们学习了两道数组基础题目:
- 两数之和:掌握暴力枚举的思路,理解双层循环的使用
- 合并两个有序数组:掌握双指针合并技巧,理解有序数组的处理方法
核心收获:
- 暴力枚举是最直接的解决方法,虽然效率不高但容易理解
- 双指针是处理有序数组的高效方法
- 合并问题需要处理剩余元素
- 数组克隆可以在修改前保留原始数据
练习建议:
- 尝试用HashMap优化两数之和
- 尝试从后向前合并两个有序数组(避免克隆)
- 思考如何合并多个有序数组
参考资源
文章标签
#LeetCode #算法 #Java #数组 #双指针
喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!