合并两个有序数组

标签: 数组 双指针 排序

难度: Easy

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

请你 合并 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 + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -109 <= nums1[i], nums2[j] <= 109

进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?

Submission

运行时间: 36 ms

内存: 15.1 MB

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """


        p1 = m-1
        p2 = n-1
        p = m + n - 1
        while p1 >= 0 and p2 >= 0:
            if nums1[p1] < nums2[p2]:
                nums1[p] = nums2[p2]
                p2 -= 1
            else:
                nums1[p] = nums1[p1]
                p1 -= 1
            p -= 1
        
        while p2 >= 0:
            nums1[p] = nums2[p2]
            p2 -= 1
            p -= 1
     

Explain

该题解使用双指针法,从两个数组的末尾开始,比较两个指针所指元素的大小,将较大的元素放入 nums1 的末尾,同时将相应的指针向前移动。重复这个过程,直到其中一个数组的元素全部放入 nums1 中。最后,如果 nums2 中还有剩余元素,将其直接复制到 nums1 的前面。

时间复杂度: O(m+n)

空间复杂度: O(1)

class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        Do not return anything, modify nums1 in-place instead.
        """

        # 初始化两个指针 p1 和 p2,分别指向 nums1 和 nums2 的末尾
        p1 = m - 1
        p2 = n - 1
        # 初始化指针 p,指向 nums1 的末尾
        p = m + n - 1
        
        # 当 p1 和 p2 都没有越界时,比较两个指针所指元素的大小
        while p1 >= 0 and p2 >= 0:
            if nums1[p1] < nums2[p2]:
                # 如果 nums2 的元素更大,将其放入 nums1 的末尾
                nums1[p] = nums2[p2]
                p2 -= 1
            else:
                # 如果 nums1 的元素更大或相等,将其放入 nums1 的末尾
                nums1[p] = nums1[p1]
                p1 -= 1
            p -= 1
        
        # 如果 nums2 中还有剩余元素,将其直接复制到 nums1 的前面
        while p2 >= 0:
            nums1[p] = nums2[p2]
            p2 -= 1
            p -= 1

Explore

双指针方法的正确执行基于几个关键假设:首先,nums1的大小至少为m+n,这是为了确保有足够的空间存放来自nums2的元素。其次,nums1和nums2必须是预先排序的。如果这些条件得到满足,该方法才能正确运行,否则结果将不正确或方法执行过程中会出现错误。

在nums1和nums2都已经是排序好的数组的情况下,我们从后向前比较和填充nums1。当其中一个数组的元素全部处理完毕时,若nums1中的元素先被处理完,这意味着nums1的元素都已经在正确的位置上,因为它们已被比较和放置。而如果是nums2中的元素还有剩余,则表明这些元素是整个合并数组中最小的,需要被复制到nums1的前端。

如果nums1的元素已经全部处理完而nums2中还有剩余元素,这些剩余元素应直接复制到nums1的前面(即nums1数组的起始位置),因为在从后向前的合并过程中,这些剩余元素一定是所有元素中最小的,且位置已经由前面的排序过程保证其正确性。

从后向前填充方法主要优势在于避免了额外的空间使用,因为它可以直接在原数组nums1上操作。当nums1有足够的空间存放两个数组合并后的所有元素时,这种方法是有效且优化的。然而,如果nums1没有预留足够空间,或者需要对原始数据的顺序保持不变,那么从前向后的填充(可能需要额外的空间来处理合并)可能更适合。