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)