题目如下:
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中。