[23]合并K个升序链表

题目描述:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

一、基础类

public static class ListNode {
    int val;
    ListNode next;
    ListNode() {
    }
    ListNode(int val) {
        this.val = val;
    }
    ListNode(int val, ListNode next) {
        this.val = val;
        this.next = next;
    }
}

二、官方解答

作者:LeetCode-Solution

方法一、顺序合并

思路与算法

我们可以想到一种最朴素的方法:用一个变量 ans 来维护以及合并的链表,第 i 次循环把第 i 个链表和 ans 合并,答案保存到 ans 中。

代码

ListNode mergeKLists(ListNode[] lists) {
    ListNode ans = null;
    for (ListNode list : lists) {
        ans = mergeTwoLists(ans, list);
    }
    return ans;
}
// 这里利用了上篇文章的方法,进阶版
ListNode mergeTwoLists(ListNode a, ListNode b) {
    if(a == null || b == null) {
        return a != null ? a : b;
    }
    ListNode head = new ListNode(0);
    ListNode tail = head, aPtr = a, bPtr = b;
    while (aPtr != null && bPtr != null) {
        if (aPtr.value < bPtr.value) {
            tail.next = aPtr;
            aPtr = aPtr.next;
        } else {
            tail.next = bPtr;
            bPtr = bPtr.next;
        }
        tail = tail.next;
    }
    tail.next = (aPtr != null ? aPtr : bPtr);
    return head.next;
}

复杂度分析

  • 时间复杂度:假设每个链表的最长长度是 n。在第一次合并后,ans 的长度为 n;第二次合并后,ans 的长度为 2×n,第 i 次合并后,ans 的长度为 i×n。第 i 次合并的时间代价是 O(n+(i−1)×n)=O(i×n),那么总的时间代价为 O(k^2 n),故渐进时间复杂度为 O(k^2 n)。
  • 空间复杂度:没有用到与 k 和 n 规模相关的辅助空间,故渐进空间复杂度为 O(1)。

方法二、分治合并

思路与算法

考虑优化方法一,用分治的方法进行合并。
将 k 个链表配对并将同一对中的链表合并;
第一轮合并以后, k 个链表被合并成了 k / 2 个链表,平均长度为 2n / k ,然后是 k / 4 个链表,k / 8 个链表等等;
重复这一过程,直到我们得到了最终的有序链表。

代码

ListNode mergeKLists2(ListNode[] lists) {
    return merge2(lists, 0, lists.length - 1);
}
ListNode merge2(ListNode[] lists, int l, int r) {
    if (l == r) return lists[l];
    if (l > r) return null;
    int mid = (l + r) >> 1;
    return mergeTwoLists(merge2(lists, l, mid), merge2(lists, mid + 1, r));
}

复杂度分析

  • 时间复杂度:考虑递归「向上回升」的过程——第一轮合并 k / 2 组链表,每一组的时间代价是 O(2n);第二轮合并 k / 4 组链表,每一组的时间代价是 O(4n)......所以总的时间代价是 O(kn×logk),故渐进时间复杂度为 O(kn×logk)。
  • 空间复杂度:递归会使用到 O(logk) 空间代价的栈空间。

方法三、使用优先队列合并

思路与算法

嘻嘻,我原本想法是用堆排,即用堆排实现的现有结构优先队列更优
这个方法和前两种方法的思路有所不同,我们需要维护当前每个链表没有被合并的元素的最前面一个,k 个链表就最多有 k 个满足这样条件的元素,每次在这些元素里面选取 val 属性最小的元素合并到答案中。在选取最小元素的时候,我们可以用优先队列来优化这个过程。

代码

static class Status implements Comparable<Status> {
    int val;
    ListNode ptr;
    Status(int val, ListNode ptr) {
        this.val = val;
        this.ptr = ptr;
    }
    public int compareTo(Status status) {
        return this.val - status.val;
    }
}
PriorityQueue<Status> queue = new PriorityQueue<>();
ListNode mergeKLists3(ListNode[] lists){
    for (ListNode node : lists) {
        if (node != null) {
            queue.offer(new Status(node.value, node));
        }
    }
    ListNode head = new ListNode(0);
    ListNode tail = head;
    while (!queue.isEmpty()) {
        Status f = queue.poll();
        tail.next = f.ptr;
        tail = tail.next;
        if (f.ptr.next != null) {
            queue.offer(new Status(f.ptr.next.value, f.ptr.next));
        }
    }
    return head.next;
}

复杂度分析

  • 时间复杂度:考虑优先队列中的元素不超过 k 个,那么插入和删除的时间代价为 O(logk),这里最多有 kn 个点,对于每个点都被插入删除各一次,故总的时间代价即渐进时间复杂度为 O(kn×logk)。
  • 空间复杂度:这里用了优先队列,优先队列中的元素不超过 k 个,故渐进空间复杂度为 O(k)。

魔都日景
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容