合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
合并两个有序链表的升级版,参考网上思路提供四种解法。
解法一:两两合并,链表数量逐次减半
比如链表数量为5个,第一次将1和4合并、2和5合并、3落单,第二次将1与3合并,第三次合并最后两条,这里k的选取以及n的更新要注意,k选择(n+1)/2而不是n/2,是为了兼顾链表个数为奇数和偶数的情况,k始终能从后半段开始。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return NULL;
int n = lists.size();
while(n > 1){
int k = (n+1)/2;
for(int i = 0; i < n/2; i++){
lists[i] = mergeTwoLists(lists[i], lists[i+k]);
}
n = k; //每次数量减半
}
return lists[0];
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
ListNode* dummy = new ListNode(-1);
ListNode* q = dummy;
while(l1 != NULL && l2 != NULL){
if(l1->val < l2->val){
q->next = l1;
l1 = l1->next;
}else{
q->next = l2;
l2 = l2->next;
}
q = q->next;
}
if(l1 != NULL) q->next = l1;
else if(l2 != NULL) q->next = l2;
return dummy->next;
}
};
解法二:可理解为解法一的递归版,分治法来两两合并链表,利用递归的思想自底向上。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
return helper(lists, 0, lists.size()-1);
}
ListNode* helper(vector<ListNode*>& lists, int start, int end){
if(start > end) return NULL;
if(start==end) return lists[start];
int mid = start + (end-start)/2;
ListNode* left = helper(lists, start, mid);
ListNode* right = helper(lists, mid+1, end);
return mergeTwoLists(left, right);
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
ListNode* dummy = new ListNode(-1);
ListNode* q = dummy;
while(l1 != NULL && l2 != NULL){
if(l1->val < l2->val){
q->next = l1;
l1 = l1->next;
}else{
q->next = l2;
l2 = l2->next;
}
q = q->next;
}
if(l1 != NULL) q->next = l1;
else if(l2 != NULL) q->next = l2;
return dummy->next;
}
};
解法三:利用最小堆的思想。
将每个链表的首元素加入最小堆中,则自动在堆中排好了序,然后取出堆首元素作为合并链表的首元素,紧接着将该元素的下一个节点加入堆中,依次类推,直到堆中最后元素被取完为止。这种解法的代码看起来也是最简洁的,平日里堆这种数据结构用得少,需要多加练习。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
//自定义函数,声明建立最小堆的标准
auto cmp = [](ListNode* &l1, ListNode* &l2){
return l1->val > l2->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> q(cmp);
for(auto node: lists){
if(node) q.push(node);
}
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
while(!q.empty()){
ListNode* top = q.top();
q.pop();
cur->next = top;
cur = cur->next;
if(top->next) q.push(top->next);
}
return dummy->next;
}
};
解法四:混合排序+哈希表
遍历所有链表的元素,找到最大值和最小值,同时在遍历的过程中,记录每个元素值及其出现的次数,最后再重建合并链表的过程中,从最小值遍历到最大值,读取哈希表中对应的元素值以及出现次数,依此新建结点。
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return NULL;
unordered_map<int, int> map;
int mn = INT_MAX, mx = INT_MIN;
for(auto node: lists){
ListNode* cur = node;
while(cur){
mn = min(mn, cur->val);
mx = max(mx, cur->val);
map[cur->val]++;
cur = cur->next;
}
}
ListNode* dummy = new ListNode(-1);
ListNode* cur = dummy;
for(int i = mn; i <= mx; i++){
if(!map.count(i)) continue;
for(int j = 0; j < map[i]; j++){
ListNode* node = new ListNode(i);
cur->next = node;
cur = cur->next;
}
}
return dummy->next;
}
};
四种解法的耗时对比如下,从下到上,依次是解法一到解法四: