算法题讲解
真题描述:
给你两个按 非递减顺序 排列的整数数组 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:
示例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 的末尾往前面填补。
这里有一点需要解释一下:
为什么是从后往前填补?
因为题目要求是把所有的值合并到 nums1 里,并不是返回一个新数组,所以说在这里 nums1 是一个长度可以存储nums2的容器。但是这个容器,它不是空的,而是前面个格子是有内容,但是后面是空的而且空的长度刚好可以放下nums2。如果我们从前往后填补,就会覆盖掉我们原来的值。而从后往前填补,我们填的都是没有内容的格子,这样会省掉很多麻烦。
由于 nums1 的数值位数和 nums2 并不一定是一样长的。所以我们还需要考虑其中一个数组提前到头的情况:
- 如果提前遍历完的数组是 nums1 ,剩下的是 nums2。那么这时意味着 nums1 的头部空出来了,直接把 nums2 整个补到 nums1 前面去即可。
- 如果提前遍历完的数组是 nums2,剩下的是 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.
*/
const merge = function(nums1, m, nums2, n) {
// 初始化两个指针的指向,初始化 nums1 尾部索引k
let i = m - 1, j = n - 1, k = m + n - 1
// 当两个数组都没遍历完时,指针同步移动
while(i >= 0 && j >= 0) {
// 取较大的值,从末尾往前填补
if(nums1[i] >= nums2[j]) {
nums1[k] = nums1[i]
i--
k--
} else {
nums1[k] = nums2[j]
j--
k--
}
}
// nums2 剩下的情况,特殊处理一下
while(j>=0) {
nums1[k] = nums2[j]
k--
j--
}
};
这个函数的时间复杂度为O(m+n),空间复杂度为O(1)。
代码讲解:
- 定义一个merge函数。
- 指针i、j、k分别初始化为nums1的末尾、nums2的末尾、nums1和nums2合并后的末尾
- 然后定义一个while循环当i和j都大于0时进入循环,判断当
nums1[i]
>=nums2[j]
时把nums1末尾的值填入 nums1的尾部 - 否则把nums2末尾的值填入 nums1的尾部
- 直到不满足i和j都大于0时退出循环
- 判断一下nums2是否还有剩余值的情况,j是否大于0,如果nums2还有剩(j>0)把nums2的剩于的值按顺序赋值给num1剩余的空位
虽然双双指针可很好的帮解决问题,但是双指针法写起来实在笨重。那我们还有没有更加快捷、简单的解题方式呢?
当然有我们可以利用JavaScript的数组方法去解答
方法二:splice+sort
这种解法非常简单明了,直接使用了JavaScript内置的数组方法splice
和sort
。
方法二
代码
第二种解法,使用JavaScript内置的数组方法`splice`和`sort`。
var merge = function(nums1, m, nums2, n) {
nums1.splice(m, nums1.length - m, ...nums2);
nums1.sort((a, b) => a - b); };
};
- 首先,通过
nums1.splice(m, nums1.length - m, ...nums2)
将nums2
数组中的元素插入到nums1
数组的后面,同时保证nums1
数组的长度不变,这样就可以省去额外的空间开销。 - 然后,调用
nums1.sort((a, b) => a - b)
将nums1数组按照从小到大的顺序排序,从而得到最终的结果。
总结
虽然这种解法非常简单,但是由于使用了sort方法,时间复杂度为 O ( ( m + n ) log ( m + n ) ) O((m+n)\log(m+n)) O((m+n)log(m+n)),相对于双指针法的 O ( m + n ) O(m+n) O(m+n)来说效率会略低一些。此外,在实际使用中,由于sort方法是基于比较的排序算法,因此当输入规模较大时,可能会导致性能问题。
写在最后
伙伴们,如果你觉得我写的文章对你有帮助就给zayyo点一个赞👍或者关注➕都是对我最大的支持。当然你也可以加我微信:IsZhangjianhao,邀你进我的前端学习交流群,一起学习前端,成为更优秀的工程师~