n个人选k个c语言_23.合并k个排序链表

合并 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;
    }
};

四种解法的耗时对比如下,从下到上,依次是解法一到解法四:

d4f5cbd8acfdea39392f6678a4d7165a.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值