题目
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
解答
解法一:逐个遍历解法 O(nk)
思路很简单,看主函数就行了。
如果 lists 不是空,就弹出一个最小的结点。
时间复杂度是最高的:O(nk) 的时间, O(1) 的空间
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode root = new ListNode(0);
ListNode cursor = root;
while(!isEmpty(lists)) {
// 选择一个最小的结点
cursor.next = popMin(lists);
cursor = cursor.next;
}
return root.next;
}
private boolean isEmpty(ListNode[] lists) {
for(int i = 0; i < lists.length; i ++) {
if(lists[i] != null) return false;
}
return true;
}
private ListNode popMin(ListNode[] lists) {
ListNode min = null;
int index = -1;
for(int i = 0; i < lists.length; i ++) {
if(lists[i] == null) continue;
if(min == null || lists[i].val < min.val) {
min = lists[i];
index = i;
}
}
lists[index] = min.next;
return min;
}
}
结果
解法二:暴力排序解法 O(nlogn)
具体步骤:
-
首先将所有链表的数据提取出来,放入到一个 List 容器中。
-
对 List 容器的内容自然排序(即升序),时间复杂度 O(nlogn)
-
对已经排序的 List 容器内的数据重新组装成链表结构
复杂度:O(nlogn) 的时间, O(n) 的空间
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
List<Integer> datas = new ArrayList<>();
// 先采集数据
for(int i = 0; i < lists.length; i ++) {
ListNode cur = lists[i];
while(cur != null) {
datas.add(cur.val);
cur = cur.next;
}
}
// 自然排序(升序)
Collections.sort(datas);
// 重新组装链表
ListNode root = new ListNode(0);
ListNode cursor = root;
for(int i = 0; i < datas.size(); i ++) {
cursor.next = new ListNode(datas.get(i));
cursor = cursor.next;
}
return root.next;
}
}
结果
解法三:小顶堆解法 O(nlogk)
可以使用实现了 堆 数据结构的集合。(TreeSet 或者 PriorityQueue)
具体流程:
- 比较器包含下述规则:
- 索引相等的覆盖掉。
- 索引不同且链表头部相同,就比较索引值。
- 否则比较链表的头部大小。
- 根据这个比较器创建一个优先队列,然后将所有的链表加入优先队列。
- 每次从优先队列内拿出一个最小结点,处理完成后将新的链表再次放入优先队列。
- 不断循环,直到优先队列内没有了链表。
- 最后返回 root.next 获得真实的头部结点 。
复杂度:O(nlogk) 的时间, O(n) 的空间
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
class Couple {
int index;
ListNode e;
Couple(int index, ListNode e) {
this.index = index;
this.e = e;
}
}
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<Couple> queue = new PriorityQueue<>(new Comparator<Couple>() {
@Override
public int compare(Couple o1, Couple o2) {
if(o1.index == o2.index) return 0;
if(o1.e.val == o2.e.val) {
return o1.index - o2.index;
}
return o1.e.val - o2.e.val;
}
});
for(int i = 0; i < lists.length; i ++) {
if(lists[i] != null) {
queue.offer(new Couple(i, lists[i]));
}
}
ListNode root = new ListNode(0);
ListNode cursor = root;
while(!queue.isEmpty()) {
Couple node = queue.poll();
cursor.next = node.e;
cursor = cursor.next;
if(node.e.next != null) {
queue.offer(new Couple(node.index, node.e.next));
}
}
return root.next;
}
}
结果
解法四:分治解法 O(nlogk)
强力推荐,很精妙的分治,有点希尔排序的味道。
核心要点:
- step 的变化情况?
- 融合哪两个?
- i 的变化情况( i += 2 * step )?
具体流程:
- 第一次:对 lists 总链表两两融合,lists 有效链表个数变为总链表的 1/2
- 第二次:对 lists 的这 1/2 个有效链表两两融合,lists 有效链表个数变为总链表的 1/4
- 第三次:对 lists 的这 1/4 个有效链表两两融合,lists 有效链表个数变为总链表的 1/8
- … …
- 第logk次: lists只有一个有效链表,它融合了全部链表。
- 返回 lists[0] 即为新链表。
合并两个有序链表可以看我上篇博客:https://blog.csdn.net/LetJava/article/details/95902521
复杂度:O(nlogk) 的时间, O(1) 的空间
代码
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
if(lists == null || lists.length == 0) return null;
int n = lists.length;
for(int step = 1; step < n; step *= 2) {
for(int i = 0; i + step < n; i += 2 * step) {
lists[i] = mergeTwoLists(lists[i], lists[i + step]);
}
}
return lists[0];
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode root = new ListNode(0);
ListNode cursor = root;
while(l1 != null && l2 != null) {
if(l1.val < l2.val) {
cursor.next = l1;
cursor = l1;
l1 = l1.next;
} else {
cursor.next = l2;
cursor = l2;
l2 = l2.next;
}
}
if(l1 != null) cursor.next = l1;
if(l2 != null) cursor.next = l2;
return root.next;
}
}