题目要求
原题目链接:23. 合并K个升序链表
题目要求如下:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例如下:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
ListNode节点结构如下:
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
解法:归并
思路
首先观察问题描述,不难看出此问题的子问题就是合并两个有序链表,所以不妨先解决合并两个有序链表的问题,再来考虑怎么合并多个有序链表。
合并两个有序链表
合并两个有序链表需要一个额外的链表头head,因为链表有序,只需要从头遍历两个链表,并对两个链表的节点进行值比较,将val值较小的节点直接尾插到链表头head后,依此进行,直到某一链表遍历完毕,再将另一链表的剩余部分直接接到新链表尾部即可。
合并过程示意图如下:
合并两个链表的方法代码如下:
public ListNode mergeList(ListNode list1, ListNode list2){
ListNode head = new ListNode();
ListNode last = head;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
last.next = list1;
list1 = list1.next;
}else{
last.next = list2;
list2 = list2.next;
}
last = last.next;
}
if(list1 != null) last.next = list1;
if(list2 != null) last.next = list2;
return head.next;
}
直接合并多个有序链表
首先最容易想到且最为简单的思路就是,设定一个新的链表result,初始可以赋值为lists[0],之后遍历lists数组,将每一次遍历的链表lists[i]与result进行合并,遍历完成后result合并完成,直接返回result。
复杂度分析
时间复杂度:O(k²n)设链表数组内每个链表的最长长度为你n。第一次合并result链表长度为n,第二次合并时为2 × n,可以推知第k次合并时result的长度为k × n,且总共需要k次合并。故总时间代价为O(n × (1+k) / 2 × k)。
空间复杂度:O(1),直接合并方式的空间复杂度优秀,只需要常数级别的额外存储空间。
归并合并多个有序链表
直接合并在时间开销上的缺点显而易见,即一次循环只合并两个链表,如果每次循环合并多个链表,即归并合并,每次将相邻的链表两两合并,总共需要log k次循环就可以合并。
归并合并示意图如下:
图片源自LeetCode,跳转链接。
完整AC代码
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists, 0, lists.length - 1);
}
// 分治
public ListNode merge(ListNode[] lists, int l, int r){
if(l == r) return lists[l];
if(l > r) return null;
int mid = (l + r) / 2;
return mergeList(merge(lists, l, mid), merge(lists, mid + 1,r));
}
// 合并两个有序链表
public ListNode mergeList(ListNode list1, ListNode list2){
ListNode head = new ListNode();
ListNode last = head;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
last.next = list1;
list1 = list1.next;
}else{
last.next = list2;
list2 = list2.next;
}
last = last.next;
}
if(list1 != null) last.next = list1;
if(list2 != null) last.next = list2;
return head.next;
}
}
复杂度分析
时间复杂度:O(log k × kn)仍设每个链表最长长度为n,第一轮合并需要合并k/2组链表,则每一组合并的时间复杂度为O(2n),第二组要合并k/4组链表,同时每一组合并的时间复杂度为O(4n),因此平均每一轮合并的时间复杂度为O(kn),总共进行log k次合并,故总时间复杂度为O(log k × kn)。
空间复杂度:O(log k),其中k为lists数组总链表数,因为递归调用会使用到栈空间。