题目
题目描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 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
题目链接
题解
解题思路
为了将多个已经按升序排列的链表合并成一个升序链表,我们采用最小堆(或优先队列)的方法来高效地管理来自不同链表的节点,并始终以 O(log k) 的时间复杂度获取下一个最小元素,其中 k 是链表的数量。下面是详细的步骤:
-
定义链表节点类:首先定义
ListNode
类表示链表节点,每个节点包含一个整数值val
和指向下一个节点的指针next
。 -
初始化最小堆:创建一个最小堆(使用 Python 的
heapq
模块),用于存储链表头节点的值、唯一索引和节点本身。这里引入唯一索引是为了确保即使两个节点有相同的值,它们也是可比较的,从而避免了直接比较ListNode
实例时可能出现的问题。 -
构建最小堆:遍历给定的链表数组
lists
,对于每一个非空链表的头节点,将其值、唯一索引以及节点对象作为一个元组加入到最小堆中。同时,维护一个唯一的索引计数器,以确保每个插入堆中的元组都有不同的索引。 -
创建结果链表的虚拟头节点:创建一个虚拟头节点
dummy
作为结果链表的起始点,并用一个指针current
来跟踪当前操作的位置。 -
提取最小值并维护堆:
- 当最小堆不为空时,从堆中弹出具有最小值的节点元组。
- 将弹出的节点连接到结果链表中,更新
current
指针指向新添加的节点。 - 如果弹出节点有后续节点,则将后续节点的值、新的唯一索引以及节点本身作为一个元组重新加入到最小堆中,继续维持堆的性质。
-
返回结果链表:当所有节点都已处理完毕后,即最小堆为空时,所有的节点都已经按照升序被添加到了结果链表中。此时返回由虚拟头节点
dummy
的next
属性指向的真实头节点,即为最终合并后的升序链表。
这种方法的时间复杂度大约是 O(N log k),其中 N 是所有链表中节点的总数,而 k 是链表的数量。空间复杂度取决于最小堆的大小,即 O(k),因为堆中最多会同时存放 k 个节点。通过这种方式,我们可以高效地将多个有序链表合并为一个有序链表。
关键点
- 使用最小堆来保证每次都能得到当前未处理节点中的最小值。
- 通过在堆中存储
(node.val, index, node)
元组的方式,确保即使两个节点的值相同也能正确比较,这里的index
是一个递增的唯一标识符。 - 创建一个虚拟头节点简化了对结果链表的操作,不需要特别处理链表为空的情况。
Python 实现
def mergeKLists(lists):
min_heap = []
dummy = ListNode()
current = dummy
index = 0
for node in lists:
if node is not None:
heappush(min_heap, (node.val, index, node))
index += 1
while min_heap:
_, _, smallest_node = heappop(min_heap)
current.next = smallest_node
current = current.next
if smallest_node.next:
index += 1
heappush(min_heap, (smallest_node.next.val, index, smallest_node.next))
return dummy.next
我们向最小堆中插入的是一个三元组 (node.val, index, node),其中 index 是一个唯一索引,用来保证即使两个节点有相同的值,它们也是可比较的。这是因为 Python 在比较元组时,会按顺序比较每一个元素,直到找到不同的元素为止。通过这种方式,我们可以确保最小堆总是能够正确地比较和排序链表节点。