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);
        }
    }
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM