題目來源於 LeetCode 第 23 號問題:合併 K 個排序連結串列。
該題在 LeetCode 官網上有關於連結串列的問題中標註為最難的一道題目:難度為 Hard ,通過率在連結串列 Hard 級別目前最低。
題目描述
合併 k 個排序連結串列,返回合併後的排序連結串列。請分析和描述演算法的複雜度。
示例:
輸入:
[
1->4->5,
1->3->4,
2->6
]
輸出: 1->1->2->3->4->4->5->6
複製程式碼
輸入
輸出
題目分析一
這裡需要將這 k 個排序連結串列整合成一個排序連結串列,也就是說有多個輸入,一個輸出,類似於漏斗一樣的概念。
因此,可以利用最小堆的概念。如果你對堆的概念不熟悉,可以戳這先了解一下~
取每個 Linked List 的最小節點放入一個 heap 中,排序成最小堆。然後取出堆頂最小的元素,放入輸出的合併 List 中,然後將該節點在其對應的 List 中的下一個節點插入到 heap 中,迴圈上面步驟,以此類推直到全部節點都經過 heap。
由於 heap 的大小為始終為 k ,而每次插入的複雜度是 logk ,一共插入了 nk 個節點。時間複雜度為 O(nklogk),空間複雜度為O(k)。
動畫演示
程式碼實現
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
//用heap(堆)這種資料結構,也就是 java 裡面的 PriorityQueue
PriorityQueue<ListNode> pq = new PriorityQueue<>(new Comparator<ListNode>() {
public int compare(ListNode a, ListNode b) {
return a.val-b.val;
}
});
ListNode ret = null, cur = null;
for(ListNode node: lists) {
if(null != node) {
pq.add(node);
}
}
while(!pq.isEmpty()) {
ListNode node = pq.poll();
if(null == ret) {
ret = cur = node;
}
else {
cur = cur.next = node;
}
if(null != node.next) {
pq.add(node.next);
}
}
return ret;
}
}
複製程式碼
題目分析二
這道題需要合併 k 個有序連結串列,並且最終合併出來的結果也必須是有序的。如果一開始沒有頭緒的話,可以先從簡單的開始:合併 兩 個有序連結串列。
合併兩個有序連結串列:將兩個有序連結串列合併為一個新的有序連結串列並返回。新連結串列是通過拼接給定的兩個連結串列的所有節點組成的。
示例:
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
複製程式碼
這道題目按照題目描述做下去就行:新建一個連結串列,比較原始兩個連結串列中的元素值,把較小的那個鏈到新連結串列中即可。需要注意的一點時由於兩個輸入連結串列的長度可能不同,所以最終會有一個連結串列先完成插入所有元素,則直接另一個未完成的連結串列直接鏈入新連結串列的末尾。
所以程式碼實現很容易寫:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//新建連結串列
ListNode dummyHead = new ListNode(0);
ListNode cur = dummyHead;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
cur = cur.next;
l1 = l1.next;
} else {
cur.next = l2;
cur = cur.next;
l2 = l2.next;
}
}
// 注意點:當有連結串列為空時,直接連線另一條連結串列
if (l1 == null) {
cur.next = l2;
} else {
cur.next = l1;
}
return dummyHead.next;
}
複製程式碼
現在回到一開始的題目:合併 K 個排序連結串列。
合併 K 個排序連結串列 與 合併兩個有序連結串列 的區別點在於操作有序連結串列的數量上,因此完全可以按照上面的程式碼思路來實現合併 K 個排序連結串列。
這裡可以參考 **歸併排序 **的分治思想,將這 K 個連結串列先劃分為兩個 K/2 個連結串列,處理它們的合併,然後不停的往下劃分,直到劃分成只有一個或兩個連結串列的任務,開始合併。
程式碼實現
根據上面的動畫,實現程式碼非常簡單也容易理解,先劃分,直到不能劃分下去,然後開始合併。
class Solution {
public ListNode mergeKLists(ListNode[] lists){
if(lists.length == 0)
return null;
if(lists.length == 1)
return lists[0];
if(lists.length == 2){
return mergeTwoLists(lists[0],lists[1]);
}
int mid = lists.length/2;
ListNode[] l1 = new ListNode[mid];
for(int i = 0; i < mid; i++){
l1[i] = lists[i];
}
ListNode[] l2 = new ListNode[lists.length-mid];
for(int i = mid,j=0; i < lists.length; i++,j++){
l2[j] = lists[i];
}
return mergeTwoLists(mergeKLists(l1),mergeKLists(l2));
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if (l1 == null) return l2;
if (l2 == null) return l1;
ListNode head = null;
if (l1.val <= l2.val){
head = l1;
head.next = mergeTwoLists(l1.next, l2);
} else {
head = l2;
head.next = mergeTwoLists(l1, l2.next);
}
return head;
}
}
複製程式碼
歡迎前往
個人網站:www.cxyxiaowu.com
公眾號:五分鐘學演算法