「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」。
题目
链接:leetcode-cn.com/problems/me…
给你两个按 非递减顺序 排列的整数数组 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
**进阶:**你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?
解题思路
思路1
- 我们不开辟新的数组,直接用 nums1 数组,用三个指针,其中一个指针 k 安排新数。
- 先比较较大的数,把大的数放到数组nums1的后面
- 如果先从前向后比较,则nums1的大部分元素都要发生移位后挪,时间复杂度会比从后向前高很多。
代码
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function (nums1, m, nums2, n) {
// 新数组元素起始的索引位置
let k = m + n - 1;
m--;
n--;
while (m >= 0 || n >= 0) {
//注意两个边界条件,m<0以及n<0,这表示一个数组已经拷贝完了
if (m < 0) {
nums1[k--] = nums2[n--];
} else if (n < 0) {
nums1[k--] = nums1[m--];
} else if (nums1[m] <= nums2[n]) {
nums1[k--] = nums2[n--];
} else {
nums1[k--] = nums1[m--];
}
}
};
思路2
先合并后排序
首先是最容易想到的,将两个数组进行合并,把num2添加到nums1的尾部,然后再对nums1数组进行排序。
var merge = function(nums1, m, nums2, n) {
// 先将nums2合并至nums1中
for(let i = 0; i<n; i++) {
nums1[m] = nums2[i];
m++;
}
// 再对nums1进行合并
nums1.sort((a, b)=>a-b);
return nums1;
};
- 缺点:没有利用到两个数组各自有序的前提
- 时间复杂度:O(m+n)log((m+n))
- 空间复杂度:O(log(m+n))
思路3
双指针法/从前到后
设有两个指针,分别指向两个数组的头部,由于最后需要返回的是nums1,故需要另设一个数组nums存放nums1初始的值。
- 两个指针分别指向nums和nums2的头部。
- 指针指向值小的那个先进入数组nums1,同时对应指针后移一位
- 当其中一个数组已经遍历完成后,另一个数组剩下的值直接添加进数组nums1中
注:在复制nums1的副本时,需要对nums1进行深复制,浅复制可能会导致数值在中途发生改变。
var merge = function(nums1, m, nums2, n) {
let nums = [m]; // 将nums1的值复制一份保存在nums中
for(let i = 0; i<m; i++) {
nums[i] = nums1[i];
}
let indicator1 = 0; // 指针指向nums1的头部
let indicator2 = 0; // 指针指向nums2的头部
let index = 0;
while(index<(m+n)) {
if(indicator1<m && indicator2<n) {
// 当nums和nums2两个数组都没遍历完时,数字较小的先进入数组nums1
nums1[index++] = (nums[indicator1]<nums2[indicator2]) ? nums[indicator1++] : nums2[indicator2++];
} else {
// 否则其中一个数组已经遍历完成而另一个数组未遍历完成,则让未遍历完的数组中的值进入数组nums1
nums1[index++] = (indicator1<m) ? nums[indicator1++] : nums2[indicator2++];
}
}
return nums1;
};
思路4
双指针/从后到前
设有两个指针,分别指向nums1和nums2的尾部;
指针指向值大的那个先进入数组nums1,同时对应指针前移一位;
当nums2数组已经遍历完成,无论nums1的指针是否遍历结束,此时nums1已有序排列,赋值即可结束。
var merge = function(nums1, m, nums2, n) {
let indicator1 = m-1; // 指针指向nums1的末尾
let indicator2 = n-1; // 指针指向nums2的末尾
let len = m+n-1;
while(indicator2 >= 0) {
// 数字较大的一个先入数组
nums1[len--] = (nums1[indicator1]>nums2[indicator2]) ? nums1[indicator1--] : nums2[indicator2--];
}
return nums1;
};
- 将代码整合一下,得到代码最精简版:
var merge = function(nums1, m, nums2, n) {
while(n>0) {
nums1[m+n-1] = (nums1[m-1]>nums2[n-1]) ? nums1[--m] : nums2[--n];
}
return nums1;
};