看下力扣第 23 题合并K个升序链表

image.png

难点: 合并 k 个有序链表的逻辑类似合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上?

这里作者介绍了一个叫二叉堆的数据结构:

二叉堆详解并实现优先级队列

链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点

ListNode mergeKLists(ListNode[] lists) {
    if (lists.length == 0) return null;
    // 虚拟头结点
    ListNode dummy = new ListNode(-1);
    ListNode p = dummy;
    // 优先级队列,最小堆
    PriorityQueue<ListNode> pq = new PriorityQueue<>(
        lists.length, (a, b)->(a.val - b.val));
    // 将 k 个链表的头结点加入最小堆
    for (ListNode head : lists) {
        if (head != null)
            pq.add(head);
    }
 
    while (!pq.isEmpty()) {
        // 获取最小节点,接到结果链表中
        ListNode node = pq.poll();
        p.next = node;
        if (node.next != null) {
            pq.add(node.next);
        }
        // p 指针不断前进
        p = p.next;
    }
    return dummy.next;
}
 
 

这个算法是面试常考题!

不懂的地方:

PriorityQueue<ListNode> pq = new PriorityQueue<>(
       lists.length, (a, b)->(a.val - b.val));

Chatgpt 解释:

这行代码创建了一个优先队列(Priority Queue)的实例 pq,这个队列用于存储类型为 ListNode 的元素。

让我一步一步解释这段代码的含义:

  1. PriorityQueue 是 Java 中的一个数据结构,它是一种队列,但是它的元素是有优先级的,按照一定的顺序出队。在这个代码片段中,你创建了一个优先队列 pq

  2. <ListNode> 告诉编译器这个队列将存储 ListNode 类型的元素。

  3. lists.length 是队列的初始容量。这个参数表示队列在开始时可以容纳的元素个数。通常,你可以将其设置为你预计的队列大小,但这个值可以根据实际需求动态扩展。

  4. (a, b)->(a.val - b.val) 是一个比较器(Comparator)函数。这个函数指定了当队列需要进行元素比较时应该如何比较元素的优先级。在这里,它比较 ListNode 元素的 val 属性,按照从小到大的顺序排列。这意味着队列将按照 val 属性的升序来排列元素,即较小的 val 值的元素将具有较高的优先级。

综合起来,这行代码创建了一个容量为 lists.length 的优先队列 pq,用于存储 ListNode 元素,而且队列中的元素将按照它们的 val 属性值从小到大排序。这通常用于合并多个有序链表,因为你可以从队列中依次取出具有最小 val 值的元素,从而实现链表的合并操作。

分析一下时间复杂度是多少?

优先队列 pq 中的元素个数最多是 k,所以一次 poll 或者 add 方法的时间复杂度是 O(logk);所有的链表节点都会被加入和弹出 pq所以算法整体的时间复杂度是 O(Nlogk),其中 k 是链表的条数,N 是这些链表的节点总数