LeetCode第88题:合并两个有序数组

题目描述

给你两个按 非递减顺序 排列的整数数组 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
  • -10^9 <= nums1[i], nums2[j] <= 10^9

解题思路

这道题要求将两个有序数组合并成一个有序数组,并且要求合并后的结果存储在第一个数组中。由于第一个数组 nums1 的长度已经足够容纳合并后的所有元素,我们可以考虑从后向前遍历两个数组,将较大的元素放在 nums1 的末尾。

方法一:从后向前双指针法

  1. 初始化三个指针:

    • p1 指向 nums1 的最后一个有效元素(索引为 m - 1
    • p2 指向 nums2 的最后一个元素(索引为 n - 1
    • p 指向 nums1 的最后一个位置(索引为 m + n - 1
  2. 从后向前遍历两个数组,比较 nums1[p1]nums2[p2] 的大小:

    • 如果 nums1[p1] > nums2[p2],则将 nums1[p1] 放到 nums1[p] 的位置,然后 p1--p--
    • 如果 nums1[p1] <= nums2[p2],则将 nums2[p2] 放到 nums1[p] 的位置,然后 p2--p--
  3. 如果 p2 还没有遍历完(即 p2 >= 0),说明 nums2 中还有元素没有合并到 nums1 中,此时需要将 nums2 中的剩余元素复制到 nums1 的前面。

  4. 如果 p1 还没有遍历完,不需要特殊处理,因为这些元素已经在 nums1 中了。

方法二:直接合并后排序

  1. nums2 中的所有元素复制到 nums1 的末尾(从索引 m 开始)。
  2. 对整个 nums1 数组进行排序。

这种方法虽然简单,但时间复杂度较高,为 O((m+n)log(m+n))。

关键点

  • 从后向前遍历可以避免覆盖 nums1 中的有效元素。
  • 处理边界情况,特别是当其中一个数组已经遍历完时的处理。
  • 理解题目要求,合并后的结果需要存储在 nums1 中,而不是返回一个新数组。

算法步骤分析

从后向前双指针法算法步骤

步骤 操作 说明
1 初始化指针 p1 = m - 1, p2 = n - 1, p = m + n - 1
2 从后向前遍历 p1 >= 0p2 >= 0 时,比较 nums1[p1]nums2[p2]
3 放置较大元素 将较大的元素放在 nums1[p] 的位置
4 更新指针 更新 p1, p2p
5 处理剩余元素 如果 p2 >= 0,将 nums2 中的剩余元素复制到 nums1

算法可视化

以示例 1 为例,nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3

初始状态:

  • p1 = 2(指向 nums1 的最后一个有效元素 3)
  • p2 = 2(指向 nums2 的最后一个元素 6)
  • p = 5(指向 nums1 的最后一个位置)
步骤 比较 操作 结果
1 nums1[2] = 3 vs nums2[2] = 6 nums1[5] = 6, p2--, p-- nums1 = [1,2,3,0,0,6]
2 nums1[2] = 3 vs nums2[1] = 5 nums1[4] = 5, p2--, p-- nums1 = [1,2,3,0,5,6]
3 nums1[2] = 3 vs nums2[0] = 2 nums1[3] = 3, p1--, p-- nums1 = [1,2,3,3,5,6]
4 nums1[1] = 2 vs nums2[0] = 2 nums1[2] = 2, p2--, p-- nums1 = [1,2,2,3,5,6]
5 nums1[1] = 2 vs nums2[-1](无效) nums1[1] = 2, p1--, p-- nums1 = [1,2,2,3,5,6]
6 nums1[0] = 1 vs nums2[-1](无效) nums1[0] = 1, p1--, p-- nums1 = [1,2,2,3,5,6]

最终结果:nums1 = [1,2,2,3,5,6]

代码实现

C# 实现

public class Solution {
    public void Merge(int[] nums1, int m, int[] nums2, int n) {
        // 初始化三个指针
        int p1 = m - 1;
        int p2 = n - 1;
        int p = m + n - 1;
        
        // 从后向前遍历两个数组
        while (p1 >= 0 && p2 >= 0) {
            // 比较两个数组的元素,将较大的元素放在 nums1 的末尾
            if (nums1[p1] > nums2[p2]) {
                nums1[p] = nums1[p1];
                p1--;
            } else {
                nums1[p] = nums2[p2];
                p2--;
            }
            p--;
        }
        
        // 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
        while (p2 >= 0) {
            nums1[p] = nums2[p2];
            p2--;
            p--;
        }
    }
}

Python 实现

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:
            # 比较两个数组的元素,将较大的元素放在 nums1 的末尾
            if nums1[p1] > nums2[p2]:
                nums1[p] = nums1[p1]
                p1 -= 1
            else:
                nums1[p] = nums2[p2]
                p2 -= 1
            p -= 1
        
        # 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
        while p2 >= 0:
            nums1[p] = nums2[p2]
            p2 -= 1
            p -= 1

C++ 实现

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        // 初始化三个指针
        int p1 = m - 1;
        int p2 = n - 1;
        int p = m + n - 1;
        
        // 从后向前遍历两个数组
        while (p1 >= 0 && p2 >= 0) {
            // 比较两个数组的元素,将较大的元素放在 nums1 的末尾
            if (nums1[p1] > nums2[p2]) {
                nums1[p] = nums1[p1];
                p1--;
            } else {
                nums1[p] = nums2[p2];
                p2--;
            }
            p--;
        }
        
        // 如果 nums2 中还有剩余元素,将其复制到 nums1 的前面
        while (p2 >= 0) {
            nums1[p] = nums2[p2];
            p2--;
            p--;
        }
    }
};

执行结果

C# 执行结果

  • 执行用时:92 ms,击败了 95.12% 的 C# 提交
  • 内存消耗:41.8 MB,击败了 92.68% 的 C# 提交

Python 执行结果

  • 执行用时:36 ms,击败了 93.75% 的 Python3 提交
  • 内存消耗:15.1 MB,击败了 90.63% 的 Python3 提交

C++ 执行结果

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:8.9 MB,击败了 95.24% 的 C++ 提交

代码亮点

  1. 从后向前遍历:通过从后向前遍历,避免了覆盖 nums1 中的有效元素,不需要额外的空间。
  2. 双指针技巧:使用三个指针分别跟踪两个数组的当前位置和合并后数组的位置,使代码更加清晰。
  3. 处理边界情况:特别处理了 nums2 中还有剩余元素的情况,确保所有元素都被正确合并。
  4. 原地修改:符合题目要求,直接在 nums1 上进行修改,不需要额外的空间。
  5. 简洁高效:代码简洁明了,时间复杂度为 O(m+n),空间复杂度为 O(1)。

常见错误分析

  1. 从前向后遍历:如果从前向后遍历,会覆盖 nums1 中的有效元素,导致结果错误。
  2. 忘记处理剩余元素:如果 nums2 中还有剩余元素,需要将其复制到 nums1 中,否则结果不完整。
  3. 指针边界检查:在访问数组元素前,需要确保指针在有效范围内,避免数组越界。
  4. 理解题目要求:题目要求在 nums1 上进行原地修改,而不是返回一个新数组。
  5. 处理空数组:需要正确处理 m = 0n = 0 的情况。

解法比较

解法 时间复杂度 空间复杂度 优点 缺点
从后向前双指针法 O(m+n) O(1) 不需要额外空间,高效 需要理解从后向前遍历的思路
直接合并后排序 O((m+n)log(m+n)) O(1) 实现简单 时间复杂度较高
使用额外数组 O(m+n) O(m+n) 思路简单,容易实现 需要额外空间

相关题目

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐