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中。