「前端刷题」88. 合并两个有序数组

93 阅读3分钟

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」。

题目

链接:leetcode-cn.com/problems/me…

给你两个按 非递减顺序 排列的整数数组 nums1nums2,另有两个整数 mn ,分别表示 nums1nums2 中的元素数目。

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

注意: 最终,合并后数组不应由函数返回,而是存储在数组 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

**进阶:**你可以设计实现一个时间复杂度为 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;
};