「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战」。
题目:给定k个有序链表,将k个有序链表合并成一个有序链表,返回这个链表。
解题思路
LeetCode 21是合并两个有序链表,那题的思路可以使用常规的移动两个指针比较大小解决,也可以使用递归来解决,本题暴力解法的思路就是类似冒泡排序一样,两两合并链表,最后返回最后一个链表即可,代码如下:
public static ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0) return null;
for(int i=0;i<lists.length;i++){
lists[i+1] = mergeTwoLists2(lists[i], lists[i+1]);
}
return lists[lists.length-1];
}
public static ListNode mergeTwoLists2(ListNode list1, ListNode list2){
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val < list2.val){
list1.next = mergeTwoLists2(list1.next, list2);
return list1;
}else {
list2.next = mergeTwoLists2(list1, list2.next);
return list2;
}
}
内部合并两个list的时间复杂度为,再加外部的循环,总时间复杂度为,空间复杂度为。最终执行耗时206ms,仅击败7%的人。
分治优化
上述代码顺序执行,其时间耗费必然很大,我们可以通过分治的方法来优化上述代码,分治思想是将一个大规模问题不断缩小,之后将小规模解逐渐合并的过程。比较典型的分治方法如归并排序,对于给定的一段很长的序列,将序列一分为二,依次递归,最终是对两个独立的元素进行排序,本题如果使用分治递归方法则最终是对两个子序列进行排序,最终调用的还是LeetCode21题的代码,代码如下:
public static ListNode mergeKLists(ListNode[] lists) {
if(lists.length==0) return null;
return mergeList(lists, 0, lists.length - 1);
}
public static ListNode mergeList(ListNode[] lists, int start, int end ){
if(end == start){
return lists[start];
}
int mid = start + (end - start) / 2;
ListNode L = mergeList(lists, start, mid);
ListNode R = mergeList(lists, mid+1, end);
return mergeTwoLists2(L, R);
}
public static ListNode mergeTwoLists2(ListNode list1, ListNode list2){
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val < list2.val){
list1.next = mergeTwoLists2(list1.next, list2);
return list1;
}else {
list2.next = mergeTwoLists2(list1, list2.next);
return list2;
}
}
时间复杂度和空间复杂度不太好计算,但其最终运行结果2ms,较优化前的方法优化了一百倍,超越了80%的人。
堆优化
还有一种更巧妙的方法,就是利用堆来解决问题,小根堆的根节点必定是最小的,我们可以遍历lists,将其中的每个元素加入堆中,每次加入都调整为小根堆,之后依次弹出堆中的元素,每次弹出也调整为小根堆,直到堆元素为空即可。Java中可以使用优先级队列来作为堆,其默认实现为小根堆,对于非基本数据类型数据,只需在初始化对象时增加排序规则即可,代码如下:
public static ListNode mergeKLists3(ListNode[] lists) {
if(lists.length==0) return null;
PriorityQueue<ListNode> queue = new PriorityQueue<>(new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
return o1.val - o2.val;
}
});
for(int i=0;i<lists.length;i++){
while (lists[i]!=null){
queue.add(lists[i]);
lists[i] = lists[i].next;
}
}
ListNode headpoint = new ListNode(-1);
ListNode cur = headpoint;
while (!queue.isEmpty()){
cur.next = queue.poll();
cur = cur.next;
}
cur.next = null;
return headpoint.next;
}
时间复杂度和空间复杂度都明显减少,但运行时间在5ms左右,比上面的分治方法满了一些。