• leetcode——Merge k Sorted Lists


    题目:

    Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

    题意:

    将k个已排好序的链表合并为一个非下降排序的链表。

    思路:

        将每个链表的表头元素取出来,建立一个小顶堆,因为k个链表中都排好序了,因此每次取堆顶的元素就是k个链表中的最小值,可以将其合并到合并链表中,再将这个元素的指针指向的下一个元素也加入到堆中,再调整堆,取出堆顶,合并链表。。。。以此类推,直到堆为空时,链表合并完毕。

        因为想练习建堆的过程,所以我没有用STL里的make_heap等方法,而是自己写的建堆函数。若想看用STL的建堆方法的,可以参考网上答案

        建堆的时间复杂度是k/2logk, 每次取出堆顶再加入元素的复杂度是logk,假设每条链表平均有n个元素,则一共有nk-k次。因此总的时间复杂度为O(nklogk)。

        还有一种思路是取出两条,用merge2Lists的方法合并为一条,再将这条和下一条用merge2Lists来合并为一条,以此类推。假设每条链表平均有n个元素,此种时间复杂度是O(2n+3n+…+kn), 为O(nk²),因此若用此法会超时。

    我的代码如下:

    class Solution {
    public:
        ListNode *mergeKLists(vector<ListNode *> &lists) {
            // 使用堆排序, 
            // 1. 选出每个链表的头来插入小顶堆中,
            // 2. 再把堆顶接入合并链表中,
            // 3. 被选出的指针后移再加入小顶堆中,回到2
            // 4. 最后所有链表都为空时,返回合并链表的头指针
            if(lists.empty()) return nullptr;
            vector<ListNode* > heap;
            // 1. 选出每个链表的头来插入小顶堆中,
            for(int i = 0; i != lists.size(); i ++){
                if(lists[i]) heap.push_back(lists[i]);
            }
            makeHeap(heap);
            // 2. 再把堆顶接入合并链表中,
            ListNode head(-1); // 合并链表的表头
            ListNode* p = &head;
            while(!heap.empty()){
                auto minNode = popHeap(heap);
                p->next = minNode; // 接入链表
                p = p->next;
                // 3. 被选出的指针后移再加入小顶堆中,回到2
                auto next = minNode->next;
                if(next) pushHeap(heap, next);
            }
            // 4. 最后所有链表都为空时,返回合并链表的头指针
            return head.next;
        }
        // 建立小顶堆
        // 自低向上
        void makeHeap(vector<ListNode*> &heap){
            // 从最后一个元素的父节点开始建立小顶堆
            for(int i = heap.size()/2; i >0 ; i --){
                minHeap(heap, i);
            }
        }
        // 调整小顶堆,以第i个元素为根建立小顶堆
        //位置从1开始,取元素时记得-1
        // 自顶向下
        void minHeap(vector<ListNode*> &heap, int i){
            int l = i*2;
            int r = l+1;
            int least(i);
            // 算出最小元素的位置
            if((l< heap.size()+1) && heap[l-1]->val<heap[i-1]->val ){
                // 如果没有超过边界并且左孩子比父亲小,则换
                least = l;
            }
            if(r<heap.size()+1 && heap[r-1]->val<heap[least-1]->val){
                // 如果没有超过边界并且右孩子最小,则换
                least = r;
            }
            if(least != i){
                swap(heap[i-1], heap[least-1]);
                minHeap(heap, least);
            }
        }
        // 在小顶堆中插入一个元素
        // 自低向上
        void pushHeap(vector<ListNode*> &heap, ListNode* p){
            heap.push_back(p);
            int child = heap.size();
            int parent = child/2;
            for(int child = heap.size(),parent = child/2; parent; child--, parent = child/2){
                if(heap[child-1]->val < heap[parent-1]->val){
                    swap(heap[child-1], heap[parent-1]);
                }
            }
        }
        // 弹出堆顶
        ListNode* popHeap(vector<ListNode*> &heap){
            swap(heap[0], heap[heap.size()-1]);
            auto p = heap.back();
            heap.pop_back();
            minHeap(heap, 1);
            return p;
        }
    };

    优化代码:

    后来想到既然堆每次加入一个元素的时候都要调整堆顶,那么每次把要添加的元素换到堆顶再调整就不用写pushHeap的函数了,当要添加的元素为空时,相当于执行popHeap函数,因此可以简化代码:

    class Solution {
    public:
        ListNode *mergeKLists(vector<ListNode *> &lists) {
            // 使用堆排序, 
            // 1. 选出每个链表的头来插入小顶堆中,
            // 2. 再把堆顶接入合并链表中,
            // 3. 被选出的指针后移再加入小顶堆中,回到2
            // 4. 最后所有链表都为空时,返回合并链表的头指针
            if(lists.empty()) return nullptr;
            vector<ListNode* > heap;
            // 1. 选出每个链表的头来插入小顶堆中,
            for(int i = 0; i != lists.size(); i ++){
               if(lists[i]) heap.push_back(lists[i]);
            }
            makeHeap(heap);
            // 2. 再把堆顶接入合并链表中,
            ListNode head(-1); // 合并链表的表头
            ListNode* p = &head;
            while(!heap.empty()){
                auto minNode = heap[0];
                p->next = minNode; // 接入链表
                p = p->next;
                // 3. 被选出的指针后移再加入小顶堆中,回到2
                auto next = minNode->next;
                if(next) {
                    heap[0] = next;
                }else{
                    swap(heap[0], heap[heap.size()-1]);
                    heap.pop_back();
                }
                minHeap(heap, 1);
            }
            // 4. 最后所有链表都为空时,返回合并链表的头指针
            return head.next;
        }
        // 建立小顶堆
        // 自低向上
        void makeHeap(vector<ListNode*> &heap){
            // 从最后一个元素的父节点开始建立小顶堆
            for(int i = heap.size()/2; i >0 ; i --){
                minHeap(heap, i);
            }
        }
        // 小顶堆,以第i个元素为根建立小顶堆
        //位置从1开始,取元素时记得-1
        // 自顶向下
        void minHeap(vector<ListNode*> &heap, int i){
            int l = i*2;
            int r = l+1;
            int least(i);
            // 算出最小元素的位置
            if((l< heap.size()+1) && heap[l-1]->val<heap[i-1]->val ){
                // 如果没有超过边界并且左孩子比父亲小,则换
                least = l;
            }
            if(r<heap.size()+1 && heap[r-1]->val<heap[least-1]->val){
                // 如果没有超过边界并且右孩子最小,则换
                least = r;
            }
            if(least != i){
                swap(heap[i-1], heap[least-1]);
                minHeap(heap, least);
            }
        }
    };
  • 相关阅读:
    数据结构(双向链表...)操作API
    本地正常,打包到服务器,页面不见了
    objective c数据封装
    2. 两数相加
    在图像中画点,画框
    面试题 16.04. 井字游戏
    12.3 运动对象分割与配准算法实现
    3. 无重复字符的最长子串
    4. 寻找两个正序数组的中位数
    python读写文件模式
  • 原文地址:https://www.cnblogs.com/skysand/p/4300711.html
Copyright © 2020-2023  润新知