题目描述
给你两个按 非递减顺序 排列的整数数组 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
-109 <= nums1[i], nums2[j] <= 109
题目分析
非递减顺序排列:关键字递增序排列,但是并非单调递增,就是从小到大且允许中间有重复的关键字。
这个题目的关键是不能新开辟一个数组,将两个数组中的关键字放进去。而是需要将重新排列好的
关键字顺序放回 nums1 数组。
题目解析
解法一:从头开始遍历
拿到这个题的最开始的思路就是,设两个指针 tmp1,tmp2,分别指向 nums1,nums2的第一个元素。
如果 tmp2 指向的元素比 tmp1 小,就将 tmp2 指向的元素插入到 tmp1 指向元素的前面,然后将 nums1 数组中 tmp1 及以后的元素都往后移一步,然后将 tmp1 和 tmp2 加 1。加 1 的原因,tmp1 开始指向的数组索引是 0,现在索引为 0 的元素已经变成 nums2 数组中的第一个元素,所以要继续判断 tmp1 原本指向的元素和 nums2 数组中下一个元素的大小,所以需要将 tmp1 和 tmp2 均加 1。
如果 tmp2 指向的元素大于等于 tmp1,就不做交换,然后将 tmp1 加 1,tmp2 不变,继续比较两个元素的大小。重复操作,直到遍历完 nums2 数组。
其实,通过上面的描述,很容易看出,这个算法是比较麻烦的,主要是在每次将 nums2 的元素插入 nums1 时,nums1 数组中的元素都需要往后挪,这会让时间复杂度大大提升。所以就有了下面的算法。
解法二:从尾开始遍历
这个算法的核心就是,从两个数组的最后面开始遍历。这样做相比从头开始遍历有什么好处呢?
由于两个数组都是有序的,当比较两个数组的最后一个元素时,较大的那个元素是不是就是两个数组中最大的那个数,那这个数是不是应该放在 nums1 数组的最后一个位置,所以这里还需要设一个指针,指向 nums1 数组中未排好序的最后一个位置。
以下面这两个数组为例:
通过第一次比较得出数组 2 中的 6 更大,将其放在 last 指向的位置;
nums1[tmp1] = 3 < nums2[tmp2] = 6, nums1[last] = nums2[tmp2]
放完过后,tmp2 指向的元素已经排好序了,那就要比较下一个数了,所以 tmp2--。同时,last 指向的位置也已经有序了,要把它重新指向最后一个未排好序的位置,所以 last--。
而此时,tmp1 指向的元素由于还没有确定它应该待的位置,所以此时 tmp1 保持不变。
当 tmp1 指向 3,tmp2 指向 2 时,last 指向 tmp1 + 1 的位置,此时,应该将 3 放到 last指向的位置,然后 tmp1--,last--,tmp2 不动。
重复以上操作,直到将 nums2 数组中所有的元素全都放到 nums1 中。
那了解了从尾遍历的大致思路过后,我们还需要考虑一些特殊的情况:
① nums1 数组中没有元素
直接将 nums2 数组中的全部元素挪到 nums1 即可,然后 return。
② nums2 数组为空
那此时 nums1 数组就是最终结果,直接 return。
③ nums1 与nums2 数组比较时,nums1 数组已经遍历完,nums2 数组还未遍历完
将 nums2 剩下的元素,直接挪到 nums1 的 last 指针及其前面的位置即可。
时间复杂度:O(m+n)
空间复杂度:O(1)
代码
在写代码的过程中还需要注意一个点:不能用 Arrays.copyOf 方法,直接将 nums2 数组拷贝给 nums1。
因为 copyOf 这个方法是在堆中重新开辟了一个新数组 copy,然后将 nums2 的数据拷贝到这个新数组 copy 中,而 copyOf 方法返回的就是这个 copy 数组的引用(地址位置),那么当把这个方法的返回值重新赋值给 nums1 时,nums1 将不再存储原来的(题目所要求的) 数组的引用,而是这个新数组 copy 的引用。那么原数组中的元素还是没有改变。
通过结果可以看到,nums1 数组并没有改变。
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
//nums1数组为空,直接将nums2元素挪到nums1即可
if(m == 0){
for(int i = 0; i < nums2.length; i++){
nums1[i] = nums2[i];
}
return;
}
//nums2为空,直接返回
if(n == 0){
return;
}
//注意:last 指向的位置,即索引,应该是数组长度 -1;
int last = m + n - 1;
//同理,nums1 和 nums2 数组的最后一个元素的索引,也是长度 -1
m--;
n--;
while(n >= 0 && m >= 0){
int tmp1 = nums1[m];
int tmp2 = nums2[n];
if(tmp2 > tmp1){
nums1[last] = tmp2;
n--;
last--;
} else{
nums1[last] = tmp1;
m--;
last--;
}
}
//nums2数组还有剩余,全部放到nums1数组中
while(n >= 0){
nums1[last] = nums2[n];
last--;
n--;
}
}
}