合并 K 个排序链表

2025年3月17日 | 阅读 8 分钟

问题陈述

在此陈述中,我们有一个链表列表,其中每个链表都按升序排序。您需要以非降序(升序)合并这些链表,从而获得一个有序列表。

样本测试用例

注意:在阅读本文之前,请尝试先想出一种方法,以便更好地理解。

方法 1:暴力枚举法

一种直接的方法是使用第一个列表初始化结果,然后遍历其余列表。对于当前列表中的每个节点,以排序方式将其插入到结果中。

以下是上述方法的 Java 实现

public class MergeKSortedLists { // 链表节点类 static class ListNode { int data; ListNode next; public ListNode(int data) { this.data = data; this.next = null; } } static void printList(ListNode node) { while (node != null) { System.out.print(node.data + " "); node = node.next; } } // 主函数,接受一个链表数组并生成排序后的输出 static ListNode mergeKLists(ListNode[] lists, int last) { for (int i = 1; i <= last; i++) { while (true) { ListNode head0 = lists[0], headI = lists[i]; if (headI == null) break; if (head0.data >= headI.data) { lists[i] = headI.next; headI.next = head0; lists[0] = headI; } else { while (head0.next != null) { if (head0.next.data >= headI.data) { lists[i] = headI.next; headI.next = head0.next; head0.next = headI; break; } head0 = head0.next; if (head0.next == null) { lists[i] = headI.next; headI.next = null; head0.next = headI; head0.next.next = null; break; } } } } } return lists[0]; } // 用于创建新节点的实用函数 static ListNode newNode(int data) { return new ListNode(data); } // 驱动程序以测试上述函数 public static void main(String[] args) { // 链表数量 int k = 3; // 每个列表中的元素数量 int n = 4; // 存储链表头节点的指针数组 ListNode[] lists = new ListNode[k]; lists[0] = newNode(1); lists[0].next = newNode(3); lists[0].next.next = newNode(5); lists[0].next.next.next = newNode(7); lists[1] = newNode(2); lists[1].next = newNode(4); lists[1].next.next = newNode(6); lists[1].next.next.next = newNode(8); lists[2] = newNode(0); lists[2].next = newNode(9); lists[2].next.next = newNode(10); lists[2].next.next.next = newNode(11); // 合并所有列表 ListNode head = mergeKLists(lists, k - 1); // 打印合并后的列表 printList(head); } }

输出

Merge K Sorted Lists

复杂度分析

时间复杂度: O(n^k-1),因为我们对每个 K 列表都遍历了 n 次。

空间复杂度: O(1)。

方法 2:使用最小堆

此解决方案采用最小堆方法。最初,通过从每个 K 个链表中插入第一个元素来构建一个最小堆。该算法通过从最小堆中提取根元素并将其附加到输出链表来继续。随后,将与提取的元素关联的链表中的下一个元素插入到最小堆中。此过程重复进行,直到最小堆中没有剩余元素,最终产生所需的结果。

算法

  1. 创建一个最小堆来存储来自 K 个链表的元素及其列表索引。
  2. 将每个链表的第一个元素及其列表索引插入到最小堆中。
  3. 初始化一个空的链表结果。
  4. 重复以下步骤,直到最小堆不为空
    • 从最小堆中提取根元素,其中包含最小值及其列表索引。
    • 将提取的元素附加到结果链表。
    • 如果与提取的元素关联的链表中存在下一个元素,则将该元素及其列表索引插入到最小堆中。
  5. 返回结果链表的头节点。

注意:最小堆确保在剩余的候选元素中最小的元素始终位于根部,从而有助于构建排序后的合并列表。当最小堆中没有更多元素时,算法终止,表示所有链表都已处理。

上述方法的 Java 实现

说明

输出

Merge K Sorted Lists

复杂度分析

时间复杂度:O(n*k*logk)

空间复杂度:O(k)

方法 3:使用分治法

该方法涉及迭代地配对排序列表,在每次迭代中将列表总数减半,直到所有列表合并。

按照以下步骤解决问题

  1. 通过以线性时间复杂度高效地合并每对列表,并利用 O(N) 空间来合并 K 个列表。
  2. 在初始循环之后,将有 K/2 个列表,每个列表的大小为 2N。随后,在第二个循环中,将有 K/4 个列表,每个列表的大小为 4N,依此类推。
  3. 迭代地继续此过程,直到只剩下一个列表。

上述方法的 Java 实现

输出

Merge K Sorted Lists

复杂度分析

时间复杂度:O(n*k^2)

空间复杂度:O(n)