← 返回题目列表

合并两个有序数组为什么要从后往前写?如何做到原地合并?

简单 第 16 / 27 题 更新于 2026/07/31
双指针有序数组原地合并

简化版

原地合并两个有序数组时,应该从后往前写。

因为 nums1 前半段保存着有效元素,从前往后写会覆盖还没比较的值;从后往前写则使用预留空间,不会破坏未处理数据。

i 指向 nums1 有效末尾,j 指向 nums2 末尾,k 指向总数组末尾,每次放较大的数。

详细版

假设 nums1 长度是 m + n,前 m 个元素有效,后 n 个位置留给合并结果。

设置:

  • i = m - 1
  • j = n - 1
  • k = m + n - 1

每次比较 nums1[i]nums2[j],把较大的放到 nums1[k],对应指针左移。

如果 nums2 还有剩余,需要继续拷贝;如果 nums1 还有剩余,不用处理,因为它已经在正确位置。

时间复杂度 O(m+n),额外空间 O(1)

完整版教学

一、为什么从前往后会覆盖数据

原地合并的难点不是合并逻辑,而是 nums1 同时承担输入和输出。若从前往后写,nums1[0] 可能还没来得及和 nums2 比较,就被新值覆盖。覆盖后原始信息丢失,后续无法恢复。

例如 nums1 = [2,5,0,0]nums2 = [1,3]。如果从前写,第一位应该放 1,但会覆盖掉 2,而 2 之后还要参与比较。

记忆钩子:原地数组合并时,哪里有空位,就优先从哪里写。

二、为什么从后往前安全

nums1 后面有 n 个空位,最终合并结果的最大值也应该在最后。我们从后往前放较大的数,写入的是空位或已经处理完的位置,不会覆盖还没用过的有效元素。这个方向正好利用了有序数组“最大值在末尾”的性质。

写入方向是否覆盖有效数据是否利用预留空间
从前往后容易覆盖不充分
从后往前不覆盖充分利用

所以三指针从后合并是这题最稳的做法。

三、三指针分别表示什么

三个指针的语义要清楚:

i:nums1 中尚未合并的最后一个有效元素
j:nums2 中尚未合并的最后一个元素
k:nums1 中下一次写入的位置

每轮比较 nums1[i]nums2[j],谁大就放到 nums1[k]。放完后,对应指针左移,k 也左移。循环保持“不变式”:k 右侧已经是最终有序结果。

四、代码模板

实现如下:

function merge(nums1, m, nums2, n) {
  let i = m - 1
  let j = n - 1
  let k = m + n - 1
  while (j >= 0) {
    if (i >= 0 && nums1[i] > nums2[j]) {
      nums1[k--] = nums1[i--]
    } else {
      nums1[k--] = nums2[j--]
    }
  }
}

外层只需要 while (j >= 0)。如果 nums1 还有剩余,它们本来就在前面正确位置;如果 nums2 有剩余,必须拷贝进去。

五、带数字推演

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

i=2(3), j=2(6), k=5 -> 放 6
i=2(3), j=1(5), k=4 -> 放 5
i=2(3), j=0(2), k=3 -> 放 3
i=1(2), j=0(2), k=2 -> 放 2

最后得到 [1,2,2,3,5,6]

六、常见误区与追问

  • 误区:从前往后合并。 会覆盖 nums1 中尚未比较的有效元素。
  • 误区:循环条件写成 i >= 0 && j >= 0。 这样会漏掉 nums2 剩余元素。
  • 误区:nums1 剩余也强行拷贝。 nums1 剩余已经在正确位置,不需要额外处理。
  • 追问:如果没有预留空间怎么办? 就不能原地从后写,需要新数组或扩容结构。
  • 追问:稳定性重要吗? 这类题通常只要求结果有序;若要求稳定,要明确相等时优先放哪个数组的元素。

这些点体现的是对“输入输出共用数组”的理解。

七、加强记忆

合并两个有序数组记成“三指针从后塞大数”。i 看 nums1 有效末尾,j 看 nums2 末尾,k 看最终写入位置。因为 nums1 尾部有空位,最大值也应该在尾部,所以从后写既符合顺序,又避免覆盖。最后只要 nums2 还有元素就继续拷贝,nums1 剩余不用动。