Merge k Sorted Lists
題目
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
題目分析
題目很短,需要我們將k個已經有序的數組合併成一個有序數組,這些數組是用鏈表的方式表示的。很明顯,這是歸併排序和分治算法結合的題目。
基礎知識
- 歸併排序
歸併排序(MERGE-SORT)是建立在歸併操作上的一種有效的排序算法,該算法是採用分治法(Divide and Conquer)的一個非常典型的應用。 將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。一般我們接觸的歸併排序是二路歸併排序,具體的過程如下:
這個過程不斷將一個數組分割,分割到兩個或者一個元素,然後又將有序的小的數組兩兩合併成一個有序的數組。這個排序算法是穩定的。
解答及算法步驟
這裏產生了兩種基本的想法,由於最後要合成一個數組,假設有A,B,C,D,E,F,G,H六個有序的數組
-
第一種想法
先合併A和B爲A,A和C合併結果爲A,A合併D作爲A,再合併E爲A,再合併F爲A,再合併G爲A,再合併H爲A,最後A即爲結果。- 算法步驟
- 新建一個鏈表存儲答案。
- 合併vector的前兩個鏈表,並將合併的鏈表刪除,將結果存儲回vector首部。
- 繼續2的操作,知道vector只有一個元素。
- 算法步驟
-
第二種想法
這可以說是對第一種想法的改進,時間複雜度更低,先合併A和B爲A1,再合併C和D爲A2,再合併E和F爲A3,再合併G和H爲A4,再合併A1和A2爲B1,再合併A3和A4爲B2,最後合併B1和B2。這剛剛好是歸併排序的逆過程。- 算法步驟
- 將vector分成兩個部分。
- 將這兩個部分的鏈表兩兩合併。
- 遞歸過2,知道只有一個元素。
- 算法步驟
源碼
- 做法一
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) {
return NULL;
}
//every two merge
while(lists.size() > 1) {
ListNode *temp = merge(lists[0], lists[1]);
lists.erase(lists.begin());
lists.erase(lists.begin());
lists.insert(lists.begin(),temp);
}
return lists[0];
}
ListNode* merge(ListNode *l1, ListNode *l2) {
if(l1 == NULL) {
return l2;
}
if(l2 == NULL) {
return l1;
}
if(l1->val < l2->val) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l2->next, l1);
return l2;
}
}
};
- 做法二
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//every two merge
if(lists.empty()) {
return NULL;
}
return merge(lists, 0, lists.size()-1);
}
ListNode* merge_two(ListNode *l1, ListNode *l2) {
if(l1 == NULL&&l2 == NULL) {
return NULL;
}
if(l1 == NULL) {
return l2;
}
if(l2 == NULL) {
return l1;
}
if(l1->val < l2->val) {
l1->next = merge_two(l1->next, l2);
return l1;
} else {
l2->next = merge_two(l2->next, l1);
return l2;
}
}
ListNode* merge(vector<ListNode*>& lists, int begin, int end) {
//condition to end
if(begin == end) {
return lists[begin];
}
int middle = (begin + end)/2;
ListNode *l1 = merge(lists, begin, middle);
ListNode *l2 = merge(lists, middle+1, end);
return merge_two(l1, l2);
}
};
複雜度分析
表面看來這兩種做法的時間複雜度是一樣的,但我們運行後發現法一解決問題平均時間爲216ms,而法二需要20ms,下面我們分別分析一下這兩種做法的時間複雜度。
- 法一
法一的事件複雜度爲O(n)
第一個和第二個需要時間爲O(n+n)
前面合併的2n個元素的與下一個合併時間爲O(2n+n)
…
最後一步的時間爲O((k-1)n+n)
最後所有時間的和爲O(n)
- 法二
法二的時間複雜度爲O(nklognk)
根據大師定理
有T(k,n)=T([],n)+T([],n)+O(kn)
和T(1,n)=O(1)
可以推出T(k,n)=O(klogkn)