LeetCode第23题
/*
Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.
Example:
Input:
[
1->4->5,
1->3->4,
2->6
]
Output: 1->1->2->3->4->4->5->6
*/
import java.util.*;
class ListNode{
int val;
ListNode next;
ListNode(){};
ListNode(int val){this.val=val;}
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
public class MergeKSortedLists{
public static void main(String[] args){
ListNode node1_1 = new ListNode(1);
ListNode node1_2 = new ListNode(4);
ListNode node1_3 = new ListNode(5);
node1_1.next = node1_2;
node1_2.next = node1_3;
ListNode node2_1 = new ListNode(1);
ListNode node2_2 = new ListNode(3);
ListNode node2_3 = new ListNode(4);
node2_1.next = node2_2;
node2_2.next = node2_3;
ListNode node3_1 = new ListNode(2);
ListNode node3_2 = new ListNode(6);
node3_1.next = node3_2;
MergeKSortedLists mksl = new MergeKSortedLists();
// 对象数组静态初始化
ListNode[] ln = new ListNode[]{node1_1,node2_1,node3_1};
ListNode result = mksl.mergeKLists(ln);
while(result != null){
System.out.println(result.val);
result = result.next;
}
}
// Java中PriorityQueue通过二叉小顶堆实现,可以用一棵完全二叉树表示
//(任意一个非叶子节点的权值,都不大于其左右子节点的权值),
// 也就意味着可以通过数组来作为PriorityQueue的底层实现。
// PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)。
// 算法的时间复杂度是 O(N log(k))
public ListNode mergeKLists(ListNode[] lists) {
// 为PriorityQueue中的元素定义比较器
Comparator<ListNode> cmp = new Comparator<ListNode>() {
@Override
public int compare(ListNode o1, ListNode o2) {
// 小顶堆的比较器只能是前减后
return o1.val-o2.val;
}
};
// https://blog.csdn.net/u010623927/article/details/87179364
// https://www.cnblogs.com/CarpenterLee/p/5488070.html
PriorityQueue<ListNode> q = new PriorityQueue<ListNode>(cmp);
for (ListNode list:lists){
// add、offer失败时分别是抛出异常和false(添加元素)
if (list != null) q.offer(list);
}
// 后期第一个节点不存储信息
ListNode head = new ListNode(0);
ListNode end = head;
// isEmpty(为空返回true
while(!q.isEmpty()){
// remove、poll失败时分别是抛出异常和null(删除元素)
end.next = q.poll();
end = end.next;
// 判断q.poll()提取出的链表是否只是包含一个元素
// 若不止一个元素,将剩下的元素没加入到PriorityQueue
if(end.next != null)q.offer(end.next);
}
return head.next;
}
// 优化版本01,所有的当前链表两两合并,减少合并次数,即减少了比较次数
// 算法的时间复杂度是 O(N k log(k))
public ListNode mergeKLists02(ListNode[] lists) {
// 判断输入的数组不为空
if(lists.length == 0) return null;
int interval = 1;
while(interval<lists.length){
// i + interval防止索引溢出,interval*2跳过已经合并的list
for (int i = 0; i + interval< lists.length; i=i+interval*2) {
// 合并后覆盖第i个索引的地址
lists[i]=mergeTwoLists(lists[i],lists[i+interval]);
}
// 每次间隔翻倍,画图出来很好理解
interval*=2;
}
return lists[0];
}
// 算法的时间复杂度是 O(N k k))
public ListNode mergeKLists01(ListNode[] lists) {
ListNode ret = null;
for(int i=0;i<lists.length;i++){
ret = mergeTwoLists(ret,lists[i]);
}
return ret;
}
// 之前题目的解,递归的效率不高,但是代码简单
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 停止条件
if (l1 == null) return l2;
if (l2 == null) return l1;
// 如何迭代
if(l1.val < l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}