题目
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
题解
无官方题解
一开始想要自己实现一个胜者组最小堆k路归并算法,感觉短时间实现不出胜者组最小堆的细节。
于是想参考Java优先队列(PriorityQueue)示例 使用Java类库中的优先队列来做一个最小堆k路归并算法。但后来自己瞎分析一下,感觉这样弄,复杂度好像是O(nlogk),后来自己想了个歪理:logn=log(k*n/k)=logk+log(n/k),除非是每个链表特别长,导致n/k特别大,那效果会不会差不多?还不如直接讨巧把全部节点读出来,然后一起排,O(nlogn)… 代码好写些
真正跑起来发现O(nlogn)和O(nlogk)还是有差距:
执行用时 : 21 ms, 在Merge k Sorted Lists的Java提交中击败了55.55% 的用户
内存消耗 : 43.6 MB, 在Merge k Sorted Lists的Java提交中击败了60.51% 的用户
/**
* 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;
}
ArrayList<ListNode> al = new ArrayList<>();
for (ListNode head : lists) {
while (head != null) {
al.add(head);
head=head.next;
}
}
al.sort(new Comparator<ListNode>() {
public int compare(ListNode o1, ListNode o2) {
if(o1.val==o2.val)return 0;//不加这句的话,130/131例子过不去,是一个数组含有大量单节点链表的例子
else return o1.val < o2.val ? -1 : 1;
}
});
ListNode head = new ListNode(-1);
if(al.size()!=0){
head.next=al.get(0);
int pIter = 1;
while (pIter<al.size()) {
al.get(pIter-1).next=al.get(pIter);
pIter++;
}
}
head = head.next;
return head;
}
}
编写过程中还碰到一个错误:“Line 15: java.lang.IllegalArgumentException: Comparison method violates its general contract!”
后来查了一下貌似是没有Comparator没有加等于的情况导致的?参考 加了注释那一行就过了。
至于最小堆的实现方法,自己最后还是参考K路归并问题小结 把答案写出来的。顺便学习了一下分治法的实现(分治法还快一些。。。)
最小堆k路归并(使用Java类库PriorityQueue):
执行用时 : 16 ms, 在Merge k Sorted Lists的Java提交中击败了64.03% 的用户
内存消耗 : 40.5 MB, 在Merge k Sorted Lists的Java提交中击败了82.81% 的用户
/**
* 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;
}
PriorityQueue<ListNode> pq = new PriorityQueue<>(lists.length, new Comparator<ListNode>() {
public int compare(ListNode o1, ListNode o2) {
return o1.val < o2.val ? -1 : 1;
}
});
for (ListNode head : lists) {
if (head != null) {
pq.offer(head);
}
}
ListNode head = new ListNode(-1);
ListNode pIter = head;
while (!pq.isEmpty()) {
ListNode min = pq.poll();
if (min.next != null) {
pq.offer(min.next);
}
ListNode pNew = new ListNode(min.val);
pIter.next = pNew;
pIter = pIter.next;
}
head = head.next;
return head;
}
}
分治法k路归并:
执行用时 : 9 ms, 在Merge k Sorted Lists的Java提交中击败了85.12% 的用户
内存消耗 : 44.2 MB, 在Merge k Sorted Lists的Java提交中击败了53.57% 的用户
/**
* 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;
}
return kMergeSort(lists, 0, lists.length - 1);
}
private ListNode kMergeSort(ListNode[] lists, int start, int end) {
if (start >= end) {
return lists[start];
}
int mid = start + (end - start) / 2;
ListNode left = kMergeSort(lists, start, mid);
ListNode right = kMergeSort(lists, mid + 1, end);
return mergeTwoLists(left, right);
}
private ListNode mergeTwoLists(ListNode left, ListNode right) {
if (left == null) {
return right;
}
if (right == null) {
return left;
}
ListNode head = new ListNode(-1);
// pIter始终指向新节点
ListNode pIter = head;
ListNode p1 = left, p2 = right;
while (p1 != null && p2 != null) {
if (p1.val < p2.val) {
pIter.val = p1.val;
p1 = p1.next;
} else {
pIter.val = p2.val;
p2 = p2.next;
}
ListNode pNew = new ListNode(-1);
pIter.next = pNew;
pIter = pIter.next;
}
while (p1 != null) {
pIter.val = p1.val;
if (p1.next != null) {
ListNode pNew = new ListNode(-1);
pIter.next = pNew;
pIter = pIter.next;
}
p1 = p1.next;
}
while (p2 != null) {
pIter.val = p2.val;
if (p2.next != null) {
ListNode pNew = new ListNode(-1);
pIter.next = pNew;
pIter = pIter.next;
}
p2 = p2.next;
}
return head;
}
}
感想
数据结构还是学的烂呀,抽时间去看书!!!
用分治法实现K路归并的方法还是可以学习一下的。