记录个人算法学习2:合并两个有序数组

51 阅读3分钟

问题描述

给你两个按 非递减顺序 排列的整数数组 nums1 **和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 **到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意: 最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 :

输入: 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 中的元素。

要求

设计实现一个时间复杂度为 O(m + n) 的算法解决此问题

思路

看到这个问题,我第一反应还是暴力求解,整一个双重for循环,但是那样时间复杂度就是 O(n²)了不符合要求,然后就想到了双指针(实际上就是两变量i和j),可能还是写这种算法相关逻辑比较少,虽说想到了双指针,但当要手撕代码时,却突然感觉无从下手,我想到了用两层for循环的方法,然后在内部控制i++ 还是 j++,但是两层for循环还是把复杂度整到了,看了别人的答案后,才知道自己缺了一个关键步骤,要从最终的结果进行反推,也就是说,结果要求复杂度是O(m + n)那就是for循环的次数是m+n,而且还有一个关键步骤,循环的时候要对nums1倒着进行遍历,因为要求是修改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) {
    // 初始化两个指针,分别指向nums1和nums2的末尾
    let i = m - 1;
    let j = n - 1;
    // 从nums1的末尾开始填充
    let k = m + n - 1;

    // 当两个数组都还有元素时,进行比较并从后往前填充nums1
    while (i >= 0 && j >= 0) {
        if (nums1[i] > nums2[j]) {
            nums1[k--] = nums1[i--];
        } else {
            nums1[k--] = nums2[j--];
        }
    }

    // 如果nums2还有剩余元素,直接复制到nums1的前面
    while (j >= 0) {
        nums1[k--] = nums2[j--];
    }
    // 如果nums1还有剩余元素,不需要操作,因为它们已经在正确的位置

    // 返回合并后的nums1
    return nums1;
};
// 示例 let nums1 = [1, 2, 3, 0, 0, 0]; 
let m = 3; let nums2 = [2, 5, 6]; 
let n = 3; 
merge(nums1, m, nums2, n); 
console.log(nums1); // 输出: [1, 2, 2, 3, 5, 6]

总结

1.打破思维定式

  • 不要局限于for(var i=0; i<n; i++)这种传统遍历方式
  • 不要局限于遍历时只能从小到大进行遍历的方式
  • 如果确定解题思路是指针的话,尽量把指针变量放到for循环外部

2.提取出问题的关键词

  • 比如问题中合并 nums2 到 nums1 中就要知道对nums1进行遍历
  • 比如问题中时间复杂度是O(m + n)就要知道遍历的次数是m+n