标题:合并K个升序链表
出处:23. 合并K个升序链表
6 级
给你一个包含 k \texttt{k} k 个链表的数组 lists \texttt{lists} lists,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入: lists = [[1,4,5],[1,3,4],[2,6]] \texttt{lists = [[1,4,5],[1,3,4],[2,6]]} lists = [[1,4,5],[1,3,4],[2,6]]
输出: [1,1,2,3,4,4,5,6] \texttt{[1,1,2,3,4,4,5,6]} [1,1,2,3,4,4,5,6]
解释:链表数组如下:
[ \texttt{[} [
1 → 4 → 5, \texttt{~~1} \rightarrow \texttt{4} \rightarrow \texttt{5,} 1→4→5,
1 → 3 → 4, \texttt{~~1} \rightarrow \texttt{3} \rightarrow \texttt{4,} 1→3→4,
2 → 6 \texttt{~~2} \rightarrow \texttt{6} 2→6
] \texttt{]} ]
将它们合并到一个有序链表中得到:
1 → 1 → 2 → 3 → 4 → 4 → 5 → 6 \texttt{1} \rightarrow \texttt{1} \rightarrow \texttt{2} \rightarrow \texttt{3} \rightarrow \texttt{4} \rightarrow \texttt{4} \rightarrow \texttt{5} \rightarrow \texttt{6} 1→1→2→3→4→4→5→6
示例 2:
输入: lists = [] \texttt{lists = []} lists = []
输出: [] \texttt{[]} []
示例 3:
输入: lists = [[]] \texttt{lists = [[]]} lists = [[]]
输出: [] \texttt{[]} []
这道题是「合并两个有序链表」的进阶,要求将 k k k 个升序链表合并成一个升序链表。合并链表的方法是,每次从所有剩余的非空链表中找到值最小的结点,将该结点合并到结果链表中,然后将对应链表的结点向后移动一步,重复该过程直到所有的链表都为空或者只有一个链表不为空。如果所有的链表都为空,则合并结束;如果只有一个链表不为空,由于该链表一定是升序的,因此直接将该链表合并到结果链表的末尾即可。
由于合并后的链表的头结点为 k k k 个链表的头结点中的值最小的结点,因此需要创建哑结点 dummyHead \textit{dummyHead} dummyHead,将合并的结点依次添加到哑结点的后面。具体做法是,创建指针 temp \textit{temp} temp 表示上一个合并的结点,初始时 temp \textit{temp} temp 指向 dummyHead \textit{dummyHead} dummyHead,每次合并结点时,将合并的结点添加到 temp \textit{temp} temp 的后面,然后将 temp \textit{temp} temp 向后移动一步,直到合并完毕。合并结束之后, dummyHead . next \textit{dummyHead}.\textit{next} dummyHead.next 即为合并后的新链表的头结点。
为了得到所有剩余的非空链表中的值最小的结点,可以遍历全部非空链表找到值最小的结点,但是该做法每次合并一个结点都需要遍历全部非空链表,因此单次合并操作的时间复杂度为 O ( k ) O(k) O(k)。可以使用优先队列将单次合并操作的时间复杂度降到 O ( log k ) O(\log k) O(logk),优先队列存储结点,队首结点为值最小的结点。
首先遍历 k k k 个链表,将每个非空链表的头结点加入优先队列,然后进行合并操作。每次合并操作时,首先从优先队列中取出剩余结点中的值最小的结点,将该结点合并到结果链表中,然后判断该结点的后一个结点是否为空,如果不为空则加入优先队列。合并操作可以保证每个链表最多有一个结点在优先队列中,且优先队列中的结点为每个链表的第一个尚未合并的结点。如果一个链表的全部结点都已经合并,则优先队列中不会有该链表的任何结点。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
ListNode dummyHead = new ListNode(0);
PriorityQueue<ListNode> pq = new PriorityQueue<ListNode>(new Comparator<ListNode>() {
public int compare(ListNode node1, ListNode node2) {
return node1.val - node2.val;
}
});
for (ListNode head : lists) {
if (head != null) {
pq.offer(head);
}
}
ListNode temp = dummyHead;
while (pq.size() > 1) {
ListNode node = pq.poll();
temp.next = node;
temp = temp.next;
node = node.next;
if (node != null) {
pq.offer(node);
}
}
if (!pq.isEmpty()) {
temp.next = pq.poll();
}
return dummyHead.next;
}
}
时间复杂度: O ( n log k ) O(n \log k) O(nlogk),其中 n n n 是全部链表的结点数, k k k 是原始链表的个数。由于优先队列中最多有 k k k 个结点,每次将结点加入优先队列和从优先队列中取出的时间复杂度是 O ( log k ) O(\log k) O(logk),每个结点都会被加入优先队列和从优先队列中取出各一次,因此总时间复杂度是 O ( n log k ) O(n \log k) O(nlogk)。
空间复杂度: O ( 1 ) O(1) O(1)。除了返回值以外,使用的空间复杂度是常数。