合并有序数组。

考查重点:有序双指针与尾段处理。双指针比较两个数组当前元素,把较小者写入结果,某一数组耗尽后追加另一尾部;若原地合并可从后向前写避免覆盖

本题目录

参考答案

核心回答

利用两个数组已经有序的条件,用双指针逐个确定下一项,时间为 O(m+n)。 允许返回新数组时,从两边开头比较,把较小者追加到结果;一边用完,再接上另一边剩余部分。

如果要求写回第一个数组,且其尾部已预留足够空间,就从后往前比较有效区间的末尾,把较大者写到结果末尾。这样不会覆盖尚未读取的输入,额外空间可以降到 O(1)。

返回新数组:通用的双指针合并

设输入按非递减顺序排列,保留重复元素,不修改输入。下面用 Python 实现:

def merge_sorted(a: list[int], b: list[int]) -> list[int]:
    i = j = 0
    result = []
    while i < len(a) and j < len(b):
        if a[i] <= b[j]:
            result.append(a[i])
            i += 1
        else:
            result.append(b[j])
            j += 1

    while i < len(a):
        result.append(a[i])
        i += 1
    while j < len(b):
        result.append(b[j])
        j += 1
    return result

例如 [1,3,5] 和 [2,3,6] 合并为 [1,2,3,3,5,6],两个 3 都保留。相等时先取 a,还能保持相等元素在各输入内的次序,以及 a 中相等项先于 b 中相等项。

每轮开始时,结果包含全部已处理元素且保持有序。剩余元素的最小值一定在两个未处理部分的开头,因此选其中较小者,就得到全体剩余元素的最小值。一边耗尽后,另一边本来有序,可以直接接上。

两个指针各自只走一遍,总时间 O(m+n);结果占 O(m+n) 空间,除输出外只用常数个变量。空数组、负数和重复值都适用。

尾部预留空间:倒序原地合并

常见的另一种约定是:nums1 长度为 m+n,前 m 项有效;nums2 长度为 n,全部有效;结果写回 nums1。

有效长度由 m 决定,不能靠是否为零识别。 零也可以是真实数据,尾部占位值不参与比较。

def merge_inplace(
    nums1: list[int], m: int, nums2: list[int], n: int
) -> None:
    i, j, write = m - 1, n - 1, m + n - 1
    while j >= 0:
        if i >= 0 and nums1[i] > nums2[j]:
            nums1[write] = nums1[i]
            i -= 1
        else:
            nums1[write] = nums2[j]
            j -= 1
        write -= 1

以 nums1=[1,3,5,0,0,0]、m=3、nums2=[2,3,6] 为例,先把 6 放在末尾,再比较 5 和 3。每次确定的是剩余元素中的最大值,已写好的后缀就是最终结果的后缀。

只要 nums2 还没用完,就有 write=i+j+1>i,写入位置在 nums1 未处理前缀右侧,所以不会踩掉待比较的数据。nums1 先耗尽时继续复制 nums2;nums2 先耗尽时,nums1 剩余前缀已经正确,可以结束。

该版本时间 O(m+n)、额外空间 O(1),依赖尾部预留空间,不能直接套到两个均无空位且要求写入其中一个定长数组的情况。

为什么不逐项插入或重新排序

逐个插入并反复定位、搬移后缀,可能造成二次量级的工作;具体成本取决于实现和数据分布。合并后调用排序也能得到正确结果,自适应排序可能利用已有的有序段,因此不能一概断言这里一定付出 O((m+n)log(m+n)) 时间。手写题可以直接说明双指针如何利用有序条件保证线性合并,无需依赖某个排序实现。

如果是多个有序数据流,可维护各流当前候选,用最小堆做多路归并。Python 的 heapq.merge() 返回惰性迭代器,要求输入流本来就有序。两个数组的手写题,用双指针更直接。

现成接口也有存储约定。例如 C++ 的 std::merge 要求输出不与输入重叠,不能把“库函数能合并”理解为支持上述尾部原地写回。

参考资料

  • 国内:Hello 算法:归并排序:借鉴合并有序子序列的思路,说明为什么只需比较未处理部分的首元素;本文只做一次合并,不包含递归排序。
  • 国内:Hello 算法:数组:根据数组中间插入需移动后缀的机制,解释逐项插入的成本,以及有效长度与预留空间的区别。
  • 国内:Doocs:合并两个有序数组:核对前 m 项有效、尾部预留空间的条件,以及倒序写入和第二段耗尽时结束的实现。
  • 国内:阿Q技术站:合并两个有序数组:对照三指针移动过程,解释先放较大值和补齐第二个数组剩余元素的原因。
  • 国外:C++ 工作草案:Merge:核对有序输入、稳定性和线性比较次数,区分 std::merge 的输出不重叠约定与本文的定制原地算法。
  • 国外:Python:heapq.merge:用于说明有序流可以惰性归并,以及归并要求各输入已经有序。

相关面经

7 篇

多益网络

软件开发工程师
秋招一面、二面
查看原面经 →
手撕:LeetCode 88. 合并两个有序数组

字节跳动

全栈开发
秋招
查看原面经 →
手撕:两个参数都是有序数组,合并成一个有序数组,不能用内置排序。

科大讯飞、首都在线、宇量晟、四川长虹、度小满、字节跳动、恒生电子、腾娱互动、金蝶、汽车之家

面经合集
查看原面经 →
二面:场景题偏多,单点登录,outh2登录。两个手撕:1.合并有序数组,2.把数组的0全移到前面然后其他元素排序。

多益网络

AI 应用开发工程师
三面
查看原面经 →
经典合并有序数组

影石创新

AI Agent
实习一面
查看原面经 →
算法题:合并两个有序数组
展开剩余 2 篇面经

字节跳动

Agent 开发
二面
查看原面经 →
手撕:合并两个有序数组

字节跳动

AI 应用开发
一面
查看原面经 →
算法:合并两个有序数组(看到题干一秒就直接写了,面试官问是不是刷过,老实承认,下次记得假装思考一下)

收录题集

2 个来源,共2 份题集

相关题目

4 题