在问 · 算法题解

23. 合并 K 个升序链表

困难链表最小堆多路归并力扣原题 ↗

题目描述

输入一个链表数组 lists,其中每个链表都已按升序排好。链表用数组表示,如 [1,4,5]。

把所有链表合并成一个升序链表,返回它的头节点。合并结果为空时返回 null。

示例 1
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]

解释:三个链表 1->4->5、1->3->4、2->6 合并后是 1->1->2->3->4->4->5->6。

示例 2
输入:lists = []
输出:[]

解释:没有链表,结果为空。

示例 3
输入:lists = [[]]
输出:[]

解释:唯一的链表是空链表,结果也为空。

数据范围

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • 每个 lists[i] 都是升序的。
  • 所有 lists[i].length 之和不超过 10^4。

解题思路:最小堆多路归并

最小堆保存每条未耗尽链的当前头部;弹出最小节点后,将其后继入堆。

为什么正确

堆中每条链最多有一个候选节点,堆顶是所有剩余节点中的最小值。

复杂度分析

总节点数 N:时间 O(N log(k+1)+k),额外空间 O(k)。

易错点

元组里加入唯一序号,避免相同值时 Python 尝试比较节点对象。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

import heapq
from itertools import count

class Solution:
    def mergeKLists(self, lists):
        heap = []
        serial = count()
        for node in lists:
            if node:
                heapq.heappush(heap, (node.val, next(serial), node))
        dummy = ListNode(0)
        tail = dummy
        while heap:
            _, _, node = heapq.heappop(heap)
            tail.next = node
            tail = node
            if node.next:
                heapq.heappush(heap, (node.next.val, next(serial), node.next))
        return dummy.next

载入编辑器,自己跑一遍 →

其他解法

顺序合并最简单但为 O(k·N);分治和最小堆都是 O(N log k),分治借用归并排序的结构,堆则每次直接取出全局最小节点。

解法 2:顺序两两合并 时间 O(k·N),空间 O(1)

用 ans 保存已合并的结果,初始为空。依次把每条链表与 ans 做一次「合并两个有序链表」,结果再赋回 ans。N 是节点总数,k 是链表条数。

只需要会合并两条链表,代码最简单。靠前的节点会被反复扫描,k 大时明显慢于堆和分治。

class Solution:
    def mergeKLists(self, lists):
        def merge_two(a, b):
            dummy = ListNode(0)
            tail = dummy
            while a and b:
                if a.val <= b.val:
                    tail.next = a; a = a.next
                else:
                    tail.next = b; b = b.next
                tail = tail.next
            tail.next = a if a else b
            return dummy.next

        ans = None
        for node in lists:
            ans = merge_two(ans, node)
        return ans

解法 3:分治两两合并 时间 O(N log k),空间 O(log k)

merge(l, r) 返回 lists[l..r] 合并后的链表。区间只剩一条时直接返回,否则从中点拆成两半分别递归,再把两个结果用「合并两个有序链表」合在一起。每个节点在递归树的每一层只被扫描一次,共 log k 层。

与堆的时间复杂度相同,不需要堆结构,额外空间只有递归栈。思路与归并排序完全一致,便于迁移。

class Solution:
    def mergeKLists(self, lists):
        def merge_two(a, b):
            dummy = ListNode(0)
            tail = dummy
            while a and b:
                if a.val <= b.val:
                    tail.next = a; a = a.next
                else:
                    tail.next = b; b = b.next
                tail = tail.next
            tail.next = a if a else b
            return dummy.next

        def merge(l, r):
            if l > r:
                return None
            if l == r:
                return lists[l]
            mid = (l + r) // 2
            return merge_two(merge(l, mid), merge(mid + 1, r))

        return merge(0, len(lists) - 1)

链表 专题的其他题目

查看 链表 全部题目 →