前言
力扣数组第88题《合并两个有序数组》:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
详解
上面是一道面试中常考的数组算法题:今天让我们来看看要怎么去解决它并且如何去实现最优的解法。
双指针
通常我们在解决关于已经排好序的数组时,就可以先想到利用双指针的思想去看看能不能实现。我们可以分别定义两个指针指向两个数组,然后进行遍历比较,找出nums2中的元素在nums1中的插入位置,确保nums2全部插入后nums1也是排好序的。
var merge = function(nums1, m, nums2, n){
// 定义两个指针
let i, j = 0;
// 开始遍历
// while 循环结束说明nums1已经遍历完了
while(i < nums1.length && j < nums2.length){
// 指针进行比较
if(nums1[i] >= nums2[j]){
// 插入nums2[j]
nums1[i, 0, nums2[j]];
// 两个指针都要往后移一位
j++;
i++;
// 删除nums1的一个0
nums1.pop();
}else{
i++;
}
}
// 接着把nums2中剩余的插入nums1
// 先移除nums1中剩余的0
for(let k = 0; k < n - j; k++){
nums1.pop()
}
// 插入nums2
for(let l = j; l < n; l++){
nums1.push(nums2[l])
}
}
在上面的代码中,我们选择使用 while 循环而不是 for 循环,因为 while 循环我们只需要考虑它的结束条件并且我们能够更方便地去控制指针的变化。
优化
我们可以在移除 nums1 中剩余的0时,进行代码的优化,我们选择用 splice 方法去删除。
var merge = function(nums1, m, nums2, n){
// 定义两个指针
let i, j = 0;
// 开始遍历
// while 循环结束说明nums1已经遍历完了
while(i < nums1.length && j < nums2.length){
// 指针进行比较
if(nums1[i] >= nums2[j]){
// 插入nums2[j]
nums1[i, 0, nums2[j]];
// 两个指针都要往后移一位
j++;
i++;
}else{
i++;
}
}
// 接着把nums2中剩余的插入nums1
// 先移除nums1中剩余的0
nums1.splice(nums1.length-n, n)
// 插入nums2
for(let l = j; l < n; l++){
nums1.push(nums2[l])
}
}
逆向双指针
因为nums1中后面的n个元素都为0,我们可以让双指针从后往前遍历,这样就可以直接覆盖掉值,而不用选择插入的方式。
var merge = function(nums1, m, nums2, n){
// 定义逆向双指针
let i = m-1, j = n-1;
// 定义数组的最后一位,用来覆盖值
let k = m + n -1;
// 开始遍历
while(i >= 0 && j >= 0){
// 每次把最大的放到新数组的最后一位
if (nums1[i] >= nums2[j]){
nums1[k] = nums1[i];
k--;
i--;
}else{
nums1[k] = nums2[j];
k--;
j--;
}
}
// 如果nums1遍历完了,nums2中还有值
// 因为已经排好序,直接加进去
while(j >= 0){
nums1[k] = nums2[j];
k--;
j--;
}
}
在这里我们使用逆向双指针让代码变得更简洁,这主要是,nums1 中后面都是0用来替换成 nums2 中的元素,所以我们可以利用这一点,使用逆向双指针去每次地把更大的值放到最后一位,而不是用插入的方式。
小结
可以看到,我们在使用双指针的前提,都是已经排序好的数组,因为如果没有排序好,我们在使用双指针进行遍历时就会出现错误,所以我们在解决关于已排序的数组问题时,我们可以优先考虑双指针去解决。