合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
public ListNode mergeKLists(ListNode[] lists) {
// 创建一个优先队列,根据节点的值进行排序
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
// 将所有链表的头节点加入优先队列
for (ListNode list : lists) {
if (list != null) {
pq.offer(list);
}
}
// 创建一个哨兵节点和当前节点指针
ListNode dummy = new ListNode(0);
ListNode current = dummy;
// 当优先队列不为空时,不断取出最小值并接入到新链表中
while (!pq.isEmpty()) {
ListNode minNode = pq.poll(); // 取出当前最小的节点
current.next = minNode; // 接入到新链表中
current = current.next; // 移动当前节点指针
// 如果取出的节点有后继节点,则将后继节点加入优先队列
if (minNode.next != null) {
pq.offer(minNode.next);
}
}
// 返回合并后链表的头节点(跳过哨兵节点)
return dummy.next;
}
这段代码实现了合并 k 个有序链表的功能,并返回合并后的有序链表。下面是对该代码的详细讲解:
函数签名
public ListNode mergeKLists(ArrayList<ListNode> lists) |
这个函数接收一个 ArrayList<ListNode>
类型的参数 lists
,其中 lists
是一个包含多个链表头节点的列表(即数组)。每个链表都是有序的,且链表的节点定义为 ListNode
类型。函数返回一个合并后的有序链表的头节点。
优先队列(PriorityQueue)
为了有效地合并这些链表,代码使用了一个最小堆(优先队列)来存储所有链表的当前最小节点。优先队列的比较器是根据节点的 val
值来定义的,确保每次从队列中取出的都是当前所有链表节点中的最小值。
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val); |
这行代码创建了一个最小堆,其中 a.val - b.val
用于定义节点之间的比较逻辑,即根据节点的 val
值进行升序排序。
初始化优先队列
接下来,代码遍历 lists
数组,将每个链表的头节点(如果非空)添加到优先队列中。
for (ListNode list : lists) { | |
if (list != null) { | |
pq.offer(list); | |
} | |
} |
合并链表
为了合并这些链表,代码使用了一个哑节点(dummy node)和一个当前节点(current node)来构建合并后的链表。哑节点是一个不存储实际数据的节点,它的 next
指针将指向合并后的链表的第一个真实节点。当前节点则用于遍历和构建合并后的链表。
ListNode dummy = new ListNode(0); | |
ListNode current = dummy; |
然后,代码进入一个循环,直到优先队列为空。在每次循环中,它执行以下操作:
- 从优先队列中取出当前所有链表节点中的最小值(即队列的头部节点),并将其连接到当前节点的
next
指针上。
ListNode minNode = pq.poll(); | |
current.next = minNode; | |
current = current.next; |
- 如果取出的节点(
minNode
)还有下一个节点(即minNode.next
不为空),则将这个下一个节点添加到优先队列中,以便在后续迭代中考虑。
if (minNode.next != null) { | |
pq.offer(minNode.next); | |
} |
返回结果
最后,函数返回哑节点的下一个节点,即合并后的链表的头节点。由于哑节点不存储实际数据,它的 next
指针将指向合并后的链表的第一个真实节点。
return dummy.next; |
总结
这段代码通过优先队列高效地合并了 k 个有序链表。它首先将所有链表的头节点添加到优先队列中,然后逐个取出队列中的最小节点,并将其连接到合并后的链表中。通过这种方式,它能够保持合并后的链表也是有序的。