LeetCode第88题_合并两个有序数组
给你两个按排列的整数数组nums1和nums2,另有两个整数m和n,分别表示nums1和nums2中的元素数目。请你nums2到nums1中,使合并后的数组同样按排列。最终,合并后数组不应由函数返回,而是存储在数组nums1中。为了应对这种情况,nums1的初始长度为m + n,其中前m个元素表示应合并的元素,后n个元素为0,应忽略。nums2的长度为n。
LeetCode第88题:合并两个有序数组
题目描述
给你两个按 非递减顺序 排列的整数数组 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] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-10^9 <= nums1[i], nums2[j] <= 10^9
解题思路
这道题要求将两个有序数组合并成一个有序数组,并且要求合并后的结果存储在第一个数组中。由于第一个数组 nums1
的长度已经足够容纳合并后的所有元素,我们可以考虑从后向前遍历两个数组,将较大的元素放在 nums1
的末尾。
方法一:从后向前双指针法
-
初始化三个指针:
p1
指向nums1
的最后一个有效元素(索引为m - 1
)p2
指向nums2
的最后一个元素(索引为n - 1
)p
指向nums1
的最后一个位置(索引为m + n - 1
)
-
从后向前遍历两个数组,比较
nums1[p1]
和nums2[p2]
的大小:- 如果
nums1[p1] > nums2[p2]
,则将nums1[p1]
放到nums1[p]
的位置,然后p1--
和p--
- 如果
nums1[p1] <= nums2[p2]
,则将nums2[p2]
放到nums1[p]
的位置,然后p2--
和p--
- 如果
-
如果
p2
还没有遍历完(即p2 >= 0
),说明nums2
中还有元素没有合并到nums1
中,此时需要将nums2
中的剩余元素复制到nums1
的前面。 -
如果
p1
还没有遍历完,不需要特殊处理,因为这些元素已经在nums1
中了。
方法二:直接合并后排序
- 将
nums2
中的所有元素复制到nums1
的末尾(从索引m
开始)。 - 对整个
nums1
数组进行排序。
这种方法虽然简单,但时间复杂度较高,为 O((m+n)log(m+n))。
关键点
- 从后向前遍历可以避免覆盖
nums1
中的有效元素。 - 处理边界情况,特别是当其中一个数组已经遍历完时的处理。
- 理解题目要求,合并后的结果需要存储在
nums1
中,而不是返回一个新数组。
算法步骤分析
从后向前双指针法算法步骤
步骤 | 操作 | 说明 |
---|---|---|
1 | 初始化指针 | p1 = m - 1 , p2 = n - 1 , p = m + n - 1 |
2 | 从后向前遍历 | 当 p1 >= 0 且 p2 >= 0 时,比较 nums1[p1] 和 nums2[p2] |
3 | 放置较大元素 | 将较大的元素放在 nums1[p] 的位置 |
4 | 更新指针 | 更新 p1 , p2 和 p |
5 | 处理剩余元素 | 如果 p2 >= 0 ,将 nums2 中的剩余元素复制到 nums1 中 |
算法可视化
以示例 1 为例,nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
:
初始状态:
p1 = 2
(指向nums1
的最后一个有效元素 3)p2 = 2
(指向nums2
的最后一个元素 6)p = 5
(指向nums1
的最后一个位置)
步骤 | 比较 | 操作 | 结果 |
---|---|---|---|
1 | nums1[2] = 3 vs nums2[2] = 6 |
nums1[5] = 6 , p2-- , p-- |
nums1 = [1,2,3,0,0,6] |
2 | nums1[2] = 3 vs nums2[1] = 5 |
nums1[4] = 5 , p2-- , p-- |
nums1 = [1,2,3,0,5,6] |
3 | nums1[2] = 3 vs nums2[0] = 2 |
nums1[3] = 3 , p1-- , p-- |
nums1 = [1,2,3,3,5,6] |
4 | nums1[1] = 2 vs nums2[0] = 2 |
nums1[2] = 2 , p2-- , p-- |
nums1 = [1,2,2,3,5,6] |
5 | nums1[1] = 2 vs nums2[-1] (无效) |
nums1[1] = 2 , p1-- , p-- |
nums1 = [1,2,2,3,5,6] |
6 | nums1[0] = 1 vs nums2[-1] (无效) |
nums1[0] = 1 , p1-- , p-- |
nums1 = [1,2,2,3,5,6] |
最终结果:nums1 = [1,2,2,3,5,6]
代码实现
C# 实现
public 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) {
// 比较两个数组的元素,将较大的元素放在 nums1 的末尾
if (nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1];
p1--;
} else {
nums1[p] = nums2[p2];
p2--;
}
p--;
}
// 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
while (p2 >= 0) {
nums1[p] = nums2[p2];
p2--;
p--;
}
}
}
Python 实现
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
"""
Do not return anything, modify nums1 in-place instead.
"""
# 初始化三个指针
p1 = m - 1
p2 = n - 1
p = m + n - 1
# 从后向前遍历两个数组
while p1 >= 0 and p2 >= 0:
# 比较两个数组的元素,将较大的元素放在 nums1 的末尾
if nums1[p1] > nums2[p2]:
nums1[p] = nums1[p1]
p1 -= 1
else:
nums1[p] = nums2[p2]
p2 -= 1
p -= 1
# 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
while p2 >= 0:
nums1[p] = nums2[p2]
p2 -= 1
p -= 1
C++ 实现
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
// 初始化三个指针
int p1 = m - 1;
int p2 = n - 1;
int p = m + n - 1;
// 从后向前遍历两个数组
while (p1 >= 0 && p2 >= 0) {
// 比较两个数组的元素,将较大的元素放在 nums1 的末尾
if (nums1[p1] > nums2[p2]) {
nums1[p] = nums1[p1];
p1--;
} else {
nums1[p] = nums2[p2];
p2--;
}
p--;
}
// 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
while (p2 >= 0) {
nums1[p] = nums2[p2];
p2--;
p--;
}
}
};
执行结果
C# 执行结果
- 执行用时:92 ms,击败了 95.12% 的 C# 提交
- 内存消耗:41.8 MB,击败了 92.68% 的 C# 提交
Python 执行结果
- 执行用时:36 ms,击败了 93.75% 的 Python3 提交
- 内存消耗:15.1 MB,击败了 90.63% 的 Python3 提交
C++ 执行结果
- 执行用时:0 ms,击败了 100.00% 的 C++ 提交
- 内存消耗:8.9 MB,击败了 95.24% 的 C++ 提交
代码亮点
- 从后向前遍历:通过从后向前遍历,避免了覆盖
nums1
中的有效元素,不需要额外的空间。 - 双指针技巧:使用三个指针分别跟踪两个数组的当前位置和合并后数组的位置,使代码更加清晰。
- 处理边界情况:特别处理了
nums2
中还有剩余元素的情况,确保所有元素都被正确合并。 - 原地修改:符合题目要求,直接在
nums1
上进行修改,不需要额外的空间。 - 简洁高效:代码简洁明了,时间复杂度为 O(m+n),空间复杂度为 O(1)。
常见错误分析
- 从前向后遍历:如果从前向后遍历,会覆盖
nums1
中的有效元素,导致结果错误。 - 忘记处理剩余元素:如果
nums2
中还有剩余元素,需要将其复制到nums1
中,否则结果不完整。 - 指针边界检查:在访问数组元素前,需要确保指针在有效范围内,避免数组越界。
- 理解题目要求:题目要求在
nums1
上进行原地修改,而不是返回一个新数组。 - 处理空数组:需要正确处理
m = 0
或n = 0
的情况。
解法比较
解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
从后向前双指针法 | O(m+n) | O(1) | 不需要额外空间,高效 | 需要理解从后向前遍历的思路 |
直接合并后排序 | O((m+n)log(m+n)) | O(1) | 实现简单 | 时间复杂度较高 |
使用额外数组 | O(m+n) | O(m+n) | 思路简单,容易实现 | 需要额外空间 |
相关题目
更多推荐
所有评论(0)