【LeetCode 23】力扣困难算法:合并 K 个升序链表 —— “ 优先队列算法 ”
题目:给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入: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
提示:
- 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
要合并 k 个已排序的链表,我们可以使用优先队列(最小堆)来有效地找到当前最小的元素。这种方法的时间复杂度为 O(N log k),其中 N 是所有链表中节点的总数,k 是链表的数量。
Java 中的优先队列:
在 Java 中,PriorityQueue 类是一个基于优先级的无界队列,使用优先级堆(默认是最小堆)实现。它提供了以下主要操作:
-
offer (E e):向优先队列添加一个元素。
-
poll ():移除并返回优先队列中优先级最高的元素(最小元素)。
-
peek ():返回优先队列中优先级最高的元素但不移除它。
-
Java 的 PriorityQueue 还允许自定义比较器(Comparator),以定义元素的排序规则。
算法步骤:
-
创建一个优先队列:优先队列用于存储每个链表的头节点,并按节点的值排序。
-
遍历链表:遍历每个链表,将每个链表的头节点添加到优先队列中。
-
合并链表:从优先队列中取出最小元素,将其添加到结果链表中,然后更新该节点的 next 指针(如果存在),并将其添加回优先队列。重复此过程,直到优先队列为空。
-
返回结果:返回合并后的链表的头节点。
复杂度分析:
-
时间复杂度:O (N log k),其中 N 是所有链表中节点的总数,k 是链表的数量。这是因为我们需要从优先队列中取出 N 个节点,每次取出操作的时间复杂度为 O(log k)。
-
空间复杂度:O (k),最坏情况下,优先队列中可能同时存储 k 个链表的头节点。
我的 Java 代码:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists==null || lists.length==0 ){
return null;
}
// 步骤1:创建一个优先队列,用于存储每个链表的头节点
PriorityQueue<ListNode> queue = new PriorityQueue<>((a,b)->(a.val-b.val));
// 步骤2:将每个链表的头节点添加到优先队列中
for(ListNode list : lists){
if(list != null){ // 注意要写这个条件!!可能 lists = [[]]
queue.offer(list);
}
}
// 步骤3:从优先队列中取出最小元素,添加到结果链表中
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while( !queue.isEmpty() ){
ListNode min = queue.poll();
cur.next = min;
cur = cur.next; // 指针后移
if(min.next != null){ // 头结点的下一个结点不为空
queue.offer(min.next); // 则把下一个结点添加到优先队列
}
}
return dummyHead.next;
}
}