「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
题目描述🌍
给你两个按 非递减顺序 排列的整数数组 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 + nnums2.length == n0 <= m, n <= 2001 <= m + n <= 200-109 <= nums1[i], nums2[j] <= 109
直接合并后排序🚀
解题思路
第一种方法也是最暴力的方法,直接将 nums2[] 追加到 nums1[] 数组尾部,然后直接排序即可完成题意。
虽然思路简单易懂,编码易实现,但完全没有利用两个数组已排序这个性质,所以导致时间复杂度和空间复杂度也是稍微逊色。
代码
Java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
for (int i = m; i < m + n; i++) {
nums1[i] = nums2[i - m];
}
Arrays.sort(nums1);
}
}
C++
class Solution {
public:
void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) {
for (int i = m; i < m + n; ++i) {
nums1[i] = nums2[i - m];
}
sort(nums1.begin(), nums1.end());
}
};
时间复杂度:,排序序列长度为 ,套用快速排序的时间复杂度即可。
空间复杂度:,排序序列长度为 ,套用快速排序的空间复杂度即可。
三指针法⚡
解题思路
指针法完美借助了非递减顺序排列的特性,同时借助了额外的空间存放排序后的数组。
其中指针 i 指向 nums1[],指针 j 指向 nums2[],比较过程中通过指针 k 不断向新建的有序数组中填充元素。
代码
Java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = 0;
int j = 0;
int[] sorted = new int[m + n];
for (int k = 0; k < m + n; k++) {
if (i == m) {
sorted[k] = nums2[j++];
} else if (j == n) {
sorted[k] = nums1[i++];
} else if (nums1[i] <= nums2[j]) {
sorted[k] = nums1[i++];
} else {
sorted[k] = nums2[j++];
}
}
// 将排序后的数组赋值给nums1[]
for (int k = 0; k < m + n; k++) {
nums1[k] = sorted[k];
}
}
}
C++
class Solution {
public:
void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) {
int p1 = 0;
int p2 = 0;
// auxiliary space
vector<int> sorted(m + n);
for (int i = 0; i < m + n; ++i) {
if (p1 == m) {
sorted[i] = nums2[p2++];
} else if (p2 == n) {
sorted[i] = nums1[p1++];
} else if (nums1[p1] <= nums2[p2]) {
sorted[i] = nums1[p1++];
} else {
sorted[i] = nums2[p2++];
}
}
nums1 = sorted;
}
};
时间复杂度:
空间复杂度:
双指针法(三指针法微改进)🏆
解题思路
为什么还要做(不跳过)这个三指针,而不直接做双指针的解法呢?因为我经常忽略了第三指针其实可以用前指针表示出来!
该双指针法与如上三指针法唯一的区别就是去除了 sortedArr 有序数组的指针,而是通过 i 与 j 间接表示。
代码
Java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = 0;
int j = 0;
int[] sortedArr = new int[m + n];
while (i < m || j < n) {
if (i == m) {
sortedArr[i + j] = nums2[j++];
} else if (j == n) {
sortedArr[i + j] = nums1[i++];
} else if (nums1[i] <= nums2[j]) {
sortedArr[i + j] = nums1[i++];
} else {
sortedArr[i + j] = nums2[j++];
}
}
for (int k = 0; k < m + n; k++) {
nums1[k] = sortedArr[k];
}
}
}
C++
class Solution {
public:
void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) {
int p1 = 0;
int p2 = 0;
vector<int> sorted(m + n);
while (p1 < m || p2 < n) {
int cur;
if (p1 == m) {
// sorted[p1 + p2] = nums[p2++] int C++: Unsequenced modification and access to 'p2'!
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] <= nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
nums1 = sorted;
}
};
时间复杂度:
空间复杂度:
🍖可以明显地看出相对方法一,三/双指针法明显降低了时间/空间复杂度;但有办法将空间复杂度缩小至 使其称为「原地算法」吗?
逆向双指针🥇
进阶:你可以设计实现一个时间复杂度为 的原地算法(空间复杂度为 )解决此问题吗?
解题思路
🔎如果 nums1[] 的长度为 m 那确实没办法实现进阶要求,但题目两个隐藏点目前只使用一个有序,而 nums1[] 长度为 m+n 仍未被充分利用。
🌅为了避免额外新建数组,我们需要将对比之后的元素放入首位/末位:
- 首位:
nums1[]先整体后移,然后通过双指针从前往后遍历,取较小元素逐一覆盖nums1[]最前面的元素。 - 末位:通过双指针从后往前逆向填充较大者,即不存在覆盖。
因为此种解法的正向双指针还需要考虑移位,比较麻烦,所以采用逆向双指针来实现。
⭐为什么 nums1[] 不会出现空位不足的情况?这里借鉴一下 LeetCode 官方的证明:
严格来说,在此遍历过程中的任意一个时刻,nums1 数组中有 个元素被放入 nums1 的后半部,nums2 数组中有 个元素被放入 nums1 的后半部,而在指针 j 的后面,nums1 数组有 个位置,由于
等价于
永远成立,因此 i 后面的位置永远足够容纳被插入的元素。
其实不用证明那么麻烦,从几个维度想一想怎么都会存在空位插入(即使覆盖也都是使用过的值)
代码
Java
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1;
int j = n - 1;
int cur;
while (i >= 0 || j >= 0) {
if (i == -1) {
cur = nums2[j--];
} else if (j == -1) {
cur = nums1[i--];
} else if (nums1[i] > nums2[j]) {
cur = nums1[i--];
} else {
cur = nums2[j--];
}
// (i + j + 1) + 1
nums1[i + j + 1 + 1] = cur;
}
}
}
C++
class Solution {
public:
void merge(vector<int> &nums1, int m, vector<int> &nums2, int n) {
int i = m - 1;
int j = n - 1;
int cur;
while (i >= 0 || j >= 0) {
if (i == -1) {
cur = nums2[j--];
} else if (j == -1) {
cur = nums1[i--];
} else if (nums1[i] >= nums2[j]) {
cur = nums1[i--];
} else {
cur = nums2[j--];
}
nums1[i + j + 1 + 1] = cur;
}
}
};
时间复杂度:
空间复杂度:,无需额外空间!
最后🌅
该篇文章为 「LeetCode」 系列的 No.11 篇,在这个系列文章中:
- 尽量给出多种解题思路
- 提供题解的多语言代码实现
- 记录该题涉及的知识点
👨💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!