1. 程式人生 > >Leetcode: 23. Merge k Sorted Lists (week1 --- hard)

Leetcode: 23. Merge k Sorted Lists (week1 --- hard)

Merge k Sorted Lists:

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

題意:

把k個已經排好序的佇列重新排成一個完整的佇列

題解:

這一道題的含義就跟歸併排序的合併步驟一樣,也就是有序佇列的合併。

  1. 一開始想直接進行k的佇列的每一次尋找最小值然後進行合併,但是看到了這一道題的難度是hard,所以上面的演算法的效率是非常低的:設元素的數目為N個,所以大概的比較次數為k*N,也就是每個元素的都需要進行k次比較,時間消耗一定會比較巨大,所以這種方法是行不通的。
  2. 這一道題是在搜尋分治演算法的時候找到的,但是覺得這一道題大概跟分治的關係也就是前面所提到的?所以可能沒有分治思維的方法。
  3. 然後就開始考慮進行排序演算法,排序演算法最快的速度也就是nlogn,效率還不如尋找最大值。
  4. 在思考排序演算法的時候剛好想到了堆排序,想起了之前資料結構老師說的堆排序是很重要的,然後想到了建堆的步驟,以及插入和刪除,而這個合併有序序列只需要提供最小值也就可以了,所以用小頂堆是一個不錯的演算法,因為一開始建堆需要的比較次數為klogk,然後每一次刪除以及插入所需要的比較次數是logk,所以總的複雜度是Nlogk,比起k*N算是一個不錯的進步了,特別是k很多的時候體現會更好。

 所以解題的程式碼如下:

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        //獲取向量的大小
        length = lists.size();
        
        //利用迭代器刪除向量中的空元素,從測例中發現的[[]]等樣例
        vector<ListNode*>::iterator it = lists.begin();
        for(;it != lists.end();)
        {
            if(*it == nullptr)
            {
                it = lists.erase(it);
                length--;
            }

            else
                //迭代器指向下一個元素位置
                ++it;
        }
        //不存在元素時返回空值
        if(length == 0){
            return nullptr;
        }
        //只有一個非空有序序列時返回該序列的指標
        if(length == 1){
            return lists[0];
        }

        //建堆
        for (int i = length / 2 - 1; i >= 0; i--)
        MinHeapDown(lists, i, length);

        //宣告結果頭節點
        ListNode * a = new ListNode(lists[0]->val);
        //宣告輔助節點
        ListNode * temp = a;
        while(length != 1){
            MinHeapDelete(lists);
            if(delete_->next != nullptr){
                MinHeapAdd(lists, length, delete_->next);
            }
            temp->next = new ListNode(lists[0]->val);
            temp = temp->next;
        }
        //用於只剩一個或者兩個有序序列時的元素的補充(因為有可能另一個被刪除了,但是還剩餘元素)
        if(length == 1){
            lists[0] = lists[0]->next;
            while(delete_->next != NULL && lists[0] != NULL){
                if(delete_->next->val > lists[0]->val){
                    temp->next = new ListNode(lists[0]->val);
                    temp = temp->next;
                    lists[0] = lists[0]->next;
                }
                else{
                    temp->next = new ListNode(delete_->next->val);
                    temp = temp->next;
                    delete_ = delete_->next;
                }
            }
            while(lists[0] != NULL){
                temp->next = new ListNode(lists[0]->val);
                temp = temp->next;
                lists[0] = lists[0]->next;
            }

            while(delete_->next != NULL){
                temp->next = new ListNode(delete_->next->val);
                temp = temp->next;
                delete_ = delete_->next;
            }
        }
        return a;
    };
    //新增新元素
    void MinHeapAdd(vector<ListNode*>& lists, int n, ListNode * new_){
        length++;
        lists[length - 1] = new_;
        MinHeapUp(lists, length - 1);
    }
    //刪除元素
    void MinHeapDelete(vector<ListNode*>& lists){
        length--;
        swap(lists[0], lists[length]);
        delete_ = lists[length];
        MinHeapDown(lists, 0, length);
    }

    //新增新的元素(新增到尾部後再進行重新排序)
    void MinHeapUp(vector<ListNode*>& lists, int n){
        int j;
        ListNode* temp = lists[n];
        j = (n - 1) / 2;
        while(j >= 0 && n != 0){
            if (lists[j]->val <= temp->val) break;
            lists[n] = lists[j];
            n = j;
            j = (n - 1) / 2;
        }
        lists[n] = temp;
    }
    //堆排序的下沉演算法(用於堆的建立以及元素的刪除)
    void MinHeapDown(vector<ListNode*>& lists, int i, int n){
        int j;
        ListNode* temp = lists[i];
        j = 2 * i + 1;
        while(j < n){
            if(j + 1 < n && lists[j + 1]->val < lists[j]->val) j++;
            if(lists[j]->val > temp->val) break;
            lists[i] = lists[j];
            i = j;
            j = 2 * i + 1;
        }
        lists[i] = temp;
    }
    //用於本地的程式碼測試(輸出結果)
    void print(ListNode *result){
        ListNode * temp = result;
        while(temp != NULL && temp->next!=NULL){
            cout << temp->val << " -> ";
            temp = temp->next;
        }
        if(temp == NULL) return ;
        cout << temp->val << endl;
    }

private:
    //用於儲存向量的大小
    int length;
    //用於儲存最近的被刪除的節點(為了記錄需要重新加入的元素的上一個結點)
    ListNode * delete_;
};

在寫這一部分程式碼的時候還是有點手生的,然後參考了一下https://blog.csdn.net/hrn1216/article/details/51465270(最小堆 構建、插入、刪除的過程圖解)這一篇部落格,因為有圖解看起來會比較好懂些。

後記:

在disscuss中查找了一下min heap C++之後看到了一個

Beats 100% method, using min heap by make_heap, pop_heap, push_heap

的答案,看了一下原來STL還自帶提供了min heap的有關的函式方法,而且還優化的這麼好,所以就學習了一波。

default (1)
template <class RandomAccessIterator>
  void make_heap (RandomAccessIterator first, RandomAccessIterator last);
custom (2)
template <class RandomAccessIterator, class Compare>
  void make_heap (RandomAccessIterator first, RandomAccessIterator last,
                  Compare comp );
Rearranges the elements in the range  [first,last) in such a way that they form a  heap.
A heap is a way to organize the elements of a range that allows for fast retrieval of the element with the highest value at any moment (with  pop_heap), even repeatedly, while allowing for fast insertion of new elements (with  push_heap).
The element with the highest value is always pointed by first. The order of the other elements depends on the particular implementation, but it is consistent throughout all heap-related functions of this header.