23. 合并K个升序链表
23. 合并K个升序链表
题目来源
题目分析
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
题目难度
- 难度:困难
题目标签
- 标签:链表、堆、递归
题目限制
- k == lists.length
- 0 <= k <= 10^4
- 0 <= lists[i].val <= 10^4
解题思路
思路:分治法
-
问题定义:
- 本题要求将
k
个已排序的链表合并为一个排序的链表。可以通过多种方式实现,例如逐一合并、使用最小堆或分治法。
- 本题要求将
-
核心算法:
- 分治法:通过递归将
k
个链表两两合并,直到只剩下一个链表为止。
- 分治法:通过递归将
-
关键步骤:
- 将链表数组递归地分割为左右两部分,分别对左右部分进行合并,最终将两部分合并为一个链表。
核心算法步骤
-
递归合并:
- 如果链表数组为空,则返回
null
。 - 如果链表数组只有一个链表,则直接返回该链表。
- 否则,将链表数组从中间分割成左右两部分,分别递归合并。
- 如果链表数组为空,则返回
-
合并两个链表:
- 使用
mergeTwoLists
函数合并两个已排序的链表。
- 使用
-
返回结果:
- 递归结束后,返回合并后的链表。
代码实现
以下是分治法的 Java 代码实现:
/**
* 23. 合并K个升序链表
* @param lists 链表数组
* @return 合并后的链表
* @apiNote 分治法,时间复杂度O(NlogK),空间复杂度O(logK)
*/
public ListNode mergeKLists(ListNode[] lists) {
if (lists.length == 0) {
return null;
}
return merge(lists, 0, lists.length - 1);
}
// 合并[left,right]的链表
public ListNode merge(ListNode[] lists, int left, int right) {
if (left == right) {
return lists[left];
}
if (left > right) {
return null;
}
int mid = (left + right) >>> 1;
return mergeTwoLists(merge(lists, left, mid), merge(lists, mid + 1, right));
}
// 使用的mergeTwoLists函数,用于合并两个有序链表
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode head = new ListNode();
ListNode cur = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
cur.next = list1 != null ? list1 : list2;
return head.next;
}
代码解读
- 递归分割:
merge
函数通过递归将lists
数组分割为两部分,并分别对其进行合并,最终返回合并后的链表。 - 合并两链表:
mergeTwoLists
函数用于合并两个已排序的链表,通过逐步比较链表节点的值,将较小的节点接到结果链表的末尾。
性能分析
- 时间复杂度:
O(NlogK)
,其中N
是所有链表的总节点数,K
是链表的数量。每次合并两个链表的时间复杂度为O(N)
,递归深度为logK
。 - 空间复杂度:
O(logK)
,递归调用栈的深度为logK
。
复杂度效果
分治
测试用例
你可以使用以下测试用例来验证代码的正确性:
// 测试用例1
ListNode[] lists1 = {null, null, null};
ListNode result1 = mergeKLists(lists1);
System.out.println(result1); // 输出: null
// 测试用例2
ListNode list2_1 = new ListNode(1, new ListNode(4, new ListNode(5)));
ListNode list2_2 = new ListNode(1, new ListNode(3, new ListNode(4)));
ListNode list2_3 = new ListNode(2, new ListNode(6));
ListNode[] lists2 = {list2_1, list2_2, list2_3};
ListNode result2 = mergeKLists(lists2);
System.out.println(result2); // 输出: 1->1->2->3->4->4->5->6
扩展讨论
优化写法
- 最小堆法:可以使用最小堆来维护每个链表的最小节点,从而以
O(NlogK)
的时间复杂度实现合并。
其他实现
- 暴力法:将所有链表节点存入一个数组中,排序后再将其连接起来,时间复杂度为
O(NlogN)
。
总结
这道题目考察了链表的合并问题,使用分治法可以有效地降低合并的时间复杂度,同时也可以通过最小堆法实现更优的时间复杂度。