LeetCode(23)Merge K Sorted Lists

本文探讨了合并K个已排序链表的问题,并分析了初版算法的时间复杂度。通过引入最小堆的数据结构,优化了算法,显著提高了效率。详细介绍了使用STL中的make_heap、pop_heap和push_heap函数实现最小堆的方法,以及如何在算法中应用这些函数。此外,文章还讨论了算法中的一些注意事项,如处理空链表和更新堆的操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目如下:

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值