题目描述

✅ 88. 合并两个有序数组

image-20260928184349693

image-20260928184349694

题意分析

两个数组都已按非递减顺序排列,也就是从小到大排列,允许出现重复值。需要把它们的所有有效元素合并,结果仍然有序,重复元素也要保留。

合并结果直接存入 nums1,不需要返回新数组。nums1 的长度是 m + n,但只有前 m 个元素参与合并,后 n 个位置只是预留空间;nums2 的 n 个元素全部参与合并。有效范围由 m 和 n 决定,不能靠元素是否为 0 判断。

解法:从后往前双指针

核心思路

[!blue]

两个数组已经有序,各自未合并部分的最后一个元素就是这一部分的最大值。比较这两个值,把较大的放到结果的最后一个空位,再继续向前填,就能得到有序结果,无需重新排序。

从后往前写,是为了保护 nums1 中还没参与比较的元素:从前往后写时,放入 nums2 的元素可能覆盖它们;而尾部有预留空间,只要 nums2 还有元素没放入,写入位置就始终在 nums1 未合并部分的右侧,不会覆盖未读数据。

这个空位关系会一直保持:待填位置需要容纳两个数组中全部剩余元素,而 nums1 的未读部分只占其中一部分。只要 nums2 还有剩余,待填区间就比 nums1 的未读区间更长,所以最右侧的写入位置不会落进未读部分。每次写入一个元素后,待填数量与剩余元素数量一起减少,关系仍然成立。

如果 nums1 的有效元素先用完,就继续把 nums2 的剩余元素向前填入。如果 nums2 先用完,nums1 剩下的元素本来就在前面,顺序和位置都正确,无需移动。因此,循环只需要判断 nums2 是否还有元素。

解题步骤

  1. 令 i = m - 1、j = n - 1,分别指向两个数组的有效末尾;k = m + n - 1 指向写入位置。
  2. 当 j >= 0 时,若 i >= 0 且 nums1[i] > nums2[j],把 nums1[i] 写入 nums1[k],并将 i 左移;否则写入 nums2[j],并将 j 左移。
  3. 将写指针 k 左移,重复上一步。
  4. j < 0 时结束;nums1 的剩余元素无需移动。

代码实现

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i = m - 1;
        int j = n - 1;
        int k = m + n - 1;

        // 倒序写入位置始终在未读的第一数组右侧;第二数组写完后,剩余前缀已就位。
        while (j >= 0) {
            // 第一数组可能先耗尽,先判断读下标再比较。
            if (i >= 0 && nums1[i] > nums2[j]) {
                nums1[k--] = nums1[i--];
            } else {
                nums1[k--] = nums2[j--];
            }
        }
    }
}
func merge(nums1 []int, m int, nums2 []int, n int) {
    i, j, k := m-1, n-1, m+n-1

    // 倒序写入位置始终在未读的第一数组右侧;第二数组写完后,剩余前缀已就位。
    for j >= 0 {
        // 第一数组可能先耗尽,先判断读下标再比较。
        if i >= 0 && nums1[i] > nums2[j] {
            nums1[k] = nums1[i]
            i--
        } else {
            nums1[k] = nums2[j]
            j--
        }
        k--
    }
}

复杂度分析

  • 时间复杂度:$O(m + n)$,每次写入一个元素,最多写入 m + n 次。
  • 空间复杂度:$O(1)$,只使用三个下标变量,直接在 nums1 中完成合并。

关键点总结

[!green]

  • 从后往前放较大值,避免覆盖 nums1 中尚未读取的元素。
  • 判断条件先检查 i >= 0,再访问 nums1[i]。
  • 循环只需保证 nums2 耗尽,nums1 的剩余部分天然有序且位置正确。

易错点总结

[!yellow]

  • 从前往后原地合并,会覆盖 nums1 的未读元素。
  • 把 i 初始化为 nums1.length - 1,会把尾部占位符当成有效数据;应使用 m - 1。
  • 主循环只比较两边都有元素的情况,却忘记继续写入 nums2 的剩余元素。
  • i < 0 后仍访问 nums1[i],会导致数组越界。

相似题目

题目 难度 关联与区别
21. 合并两个有序链表 简单 同样合并两个有序序列,本题数组前段可能被写入覆盖,因此常从大到小往后填。
977. 有序数组的平方 简单 同样从两端获取较大候选并从输出末尾写入,避免额外排序。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/16510261
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!