1. 程式人生 > >LeetCode(23)Merge K Sorted Lists

LeetCode(23)Merge K Sorted Lists

題目如下:

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

分析:

第一次思考的時候,想得比較自然。打算仿照歸併排序的思路來進行。每次都在K個元素中選擇一個最小的出來。每次選擇最小的時間複雜度是O(K),這樣的事情一共做了O(N)次(假設一共有N個元素)。

另外注意需要考慮一些情況,比如 lists[i]如果在作為函式的輸入,可能會在一開始就是NULL;處理到中途,可能lists[i]對應的單鏈表從非空變化為了空。

這是第一版Solution.h函式

//第一版 Solution.h
#ifndef LeetCodeOJ_022_MergeKList_Solution_h
#define LeetCodeOJ_022_MergeKList_Solution_h

#include <iostream>
#include <vector>
using namespace std;

struct ListNode {
     int val;
     ListNode *next;
     ListNode(int x) : val(x), next(NULL) {}
};

class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        // 0 預處理,去掉一開始就為空的節點
        for(int i=0;i<lists.size();i++) {
            if(lists[i]==NULL)
                lists.erase(lists.begin()+i);
        }
        ListNode* final_list_head=NULL;
        ListNode* final_list_tail=NULL;
        while(lists.size()>0){
            int small_list_index=-1;
            ListNode* small_list_node=new ListNode(INT_MAX);
            vector<int> change_lists_index;
            //1 找到當前最小的節點
            for(int i=0;i<(int)lists.size();i++) {
                ListNode* current_list_node = lists[i];
                if(current_list_node==NULL) {
                    change_lists_index.push_back(i);//記錄已經消耗完畢的list
                    continue;
                }
                if(current_list_node->val<small_list_node->val) {
                    small_list_node = current_list_node;
                    small_list_index=i;
                }
            }
            
            if(lists.size()==1&&lists[0]==NULL)//當只剩最後一個節點(必然為NULL)時,退出。
                break;

            //2 把當前最小節點放入新list
            if(final_list_head==NULL){
                ListNode* p = new ListNode(small_list_node->val);
                final_list_head = p;
                final_list_tail = p;
            }else{
                ListNode* p = new ListNode(small_list_node->val);
                final_list_tail->next = p;
                final_list_tail = p;
            }
            lists[small_list_index]=small_list_node->next;

            
            //3 對已經消耗完畢的list做處理
            if(!change_lists_index.empty()) {
                for(int j=(int)change_lists_index.size()-1;j>=0;j--) //先大後小進行刪除,這樣最方便。每次刪除都不引起下一個帶刪項的下標的變動
                {
                    lists.erase(lists.begin()+change_lists_index[j]);
                }
            }
        }//while
        
        return final_list_head;
    }
};
#endif

這是第一版的main函式:
第一版的main函式
#include <iostream>
#include "Solution.h"
ListNode* make_list(int* in_arr, int len){
    ListNode* p_head = NULL;
    ListNode* p_tail = NULL;
    for(int i=0;i<len;i++){
        ListNode* p_node=new ListNode(in_arr[i]);
        if(p_head == NULL) {
            p_head = p_node;
            p_tail = p_node;
        }else {
            p_tail->next=p_node;
            p_tail=p_tail->next;
        }
    }
    return p_head;
}
void print_list(ListNode* ln){
    std::cout<<"list elements are = ";
    while(ln!=NULL) {
        std::cout<<ln->val<<"\t";
        ln=ln->next;
    }
    std::cout<<std::endl;
    return;
}
int main(int argc, const char * argv[])
{
    int array1[]={1};
//    int array2[]={2,9};
//    int array3[]={3,6};
//    int array1[]={-30,1,4,7,10};
//    int array2[]={-20,0,2,5,8,11,14};
//    int array3[]={3,6,9,12,15,17,19,20};
    vector<ListNode*> vec_all;
    ListNode* ln0=NULL;
    ListNode* ln1 = make_list(array1,sizeof(array1)/sizeof(int));
//    ListNode* ln2 = make_list(array2,sizeof(array2)/sizeof(int));
//    ListNode* ln3 = make_list(array3,sizeof(array3)/sizeof(int));
    print_list(ln1);
//    print_list(ln2);
//  print_list(ln3);
    
    vec_all.push_back(ln0);
    vec_all.push_back(ln1);
//  vec_all.push_back(ln2);
//    vec_all.push_back(ln3);
    Solution s1;
    ListNode* ln_all;
    ln_all = s1.mergeKLists(vec_all);
    print_list(ln_all);
    std::cout << "022 Hello, World!\n";
    return 0;
}

第二次考慮是否有改進的空間了,每次從K個元素中找到最小的1個元素放入新連結串列的過程,其實就是每次找到最小的1個數的過程,這個過程可以用STL中的Algorithm庫中的最小堆來實現。這樣每次從K個元素中找到最小的元素只需要O(1)的時間複雜度,然後調整新的堆也只需要O(lgK)的時間複雜度。這樣答案如下,參考自官網

ListNode *mergeKLists(vector<ListNode *> &lists) {
    vector<ListNode*>::iterator it = lists.begin();
    while(it != lists.end()) {
        if(*it == NULL) lists.erase(it);
        else ++it;
    }
    if(lists.size() < 1) return NULL;

    ListNode *head = NULL, *cur = NULL;
    make_heap(lists.begin(), lists.end(), comp());

    while(lists.size() > 0) {
        if(head == NULL) head = cur = lists[0];
        else cur = cur->next = lists[0];

        pop_heap(lists.begin(), lists.end(), comp());
        int last = lists.size() - 1;
        if(lists[last]->next != NULL) {
            lists[last] = lists[last]->next;
            push_heap(lists.begin(), lists.end(), comp());
        }
        else lists.pop_back();
    }

    return head;
}

 class comp {
 public:
    bool operator() (const ListNode* l, const ListNode* r) const {
        return (l->val > r->val);
    }
};

需要注意的是,STL的Algorithm中的make_heap()函式是這樣的。
1 Rearranges the elements in the range [first,last) in such a way that they form a heap.
2 The element with the highest value is always pointed by first.
3 The elements are compared using operator< (for the first version), or comp (for the second): The element with the highest value is an element for which this would return false when compared to every other element in the range.
STL的Algorithm中,建立最大對使用的比較操作符是<。所以,根據本題的需求,要建立最小堆,應該使用的比較操作符是>。

update: 2014-12-23

使用最小堆來做,其實思路和上面一樣。

//180 ms
struct CompareListNode
{
    bool operator()(const ListNode* a, const ListNode* b) const
    {
        return a->val > b->val; // 大於符號用於建立最小堆。
    }
};
class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        if (lists.size() == 0) return NULL;
        vector<ListNode*> vector_for_heap;
        for ( int i = 0; i < lists.size(); ++i) {
            if (lists[i] == NULL) continue;
            vector_for_heap.push_back(lists[i]);
            lists[i] = lists[i]->next;
        }
        std::make_heap(vector_for_heap.begin(), vector_for_heap.end(), CompareListNode());
        ListNode* dummy_head = new ListNode(-1);
        ListNode* tail = dummy_head;
        ListNode* refresh_node = NULL;
        while (vector_for_heap.size() > 0) {
            refresh_node = vector_for_heap.front();
            tail->next = vector_for_heap.front();
            tail = tail->next;
            std::pop_heap(vector_for_heap.begin(), vector_for_heap.end(), CompareListNode()); //注意make_heap(), pop_heap(), push_heap都需要寫比較函式作為引數,如果已經定義了比較函式的話。
            vector_for_heap.pop_back();
            refresh_node = refresh_node->next;
            if (refresh_node != NULL) {
                vector_for_heap.push_back(refresh_node);
                push_heap(vector_for_heap.begin(), vector_for_heap.end(), CompareListNode());
            }
        }
        tail->next = NULL;
        return dummy_head->next;
    }
};

update:

2015-03-23

//47ms
struct CompareListElement {
    bool operator ()(const ListNode* a, const ListNode* b) const
    {
        return (a->val > b->val);  //NOTE1: 用大於符號建立小堆
    }
};

class Solution {
public:
    ListNode *mergeKLists(vector<ListNode *> &lists) {
        vector<ListNode*> vecK;
        ListNode* dummyHead = new ListNode(0);
        ListNode* tail = dummyHead;
        for (int i = 0; i < lists.size(); ++i) {
            if (lists[i] != NULL) {//NOTE2: 判斷是否為NULL
                vecK.push_back(lists[i]);
            }
        }
        
        std::make_heap(vecK.begin(), vecK.end(), CompareListElement()); //NOTE3.1 comparator 要麼全部使用,要麼全部不使用
        while(!vecK.empty()) {
            tail->next = vecK.front();
            tail = tail->next;
            std::pop_heap(vecK.begin(), vecK.end(), CompareListElement()); //NOTE3.2 comparator 要麼全部使用,要麼全部不使用
            vecK.pop_back();
            if(tail->next != NULL) {
                vecK.push_back(tail->next);
                std::push_heap(vecK.begin(), vecK.end(), CompareListElement()); //NOTE3.3 comparator 要麼全部使用,要麼全部不使用
            }
        }
        return dummyHead->next;
    }
};


小結擴充套件:

1. 一號坑: make_heap(), pop_heap(), push_heap(), 注意在使用comparator的時候,要保持一致,要麼都寫要麼都不寫。

2. 二號坑: C++ algorithm預設建立的是max heap,如果要建立min heap,要過載comparator,符號為>。

3. 三號坑: heap的push()和vector的push()要搭配使用, pop()同理。

4. 四號坑:   Validate  Input.   Line 15 判斷元素是否為NULL,不為NULL才加入初始的vecK中。