【使用大小顶堆(优先级队列priority_queue)解决的题目(链表+数组)】

Leetcode23.合并k个升序链表



一、题目描述

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例描述

二、求解思路

因为需要将链表合并到一个新的链表中,因此需要创建一个新的链表用于存放这个合成链表。假设一共有k个链表那么如何快速得到 k 个节点中的最小节点,接到结果链表上?

  • 此处可以想到使用小顶堆,它会自动按由小到大的顺序排列。(c++中可以用优先队列)
  • 因为链表数组中的每个链表都是按照升序进行排列,因此只需将每个链表的头节点存入到小顶堆中,然后每次取出小顶堆的头节点(即这些节点中最小的节点),然后再将这个最小节点所在链表的下一节点存入到小顶堆中,依此类推…此时在队列中只需维持这k个节点即可。
  • 另外,对于优先队列需要自定义比较函数,存在几种定义方式,会在下面的代码中给出。

三、代码

1.采用lambda表达式自定义比较函数

class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int k=lists.size();
        ListNode* dummy=new ListNode(0);
        ListNode* p=dummy;
        //采用lambda表达式自定义比较函数
        priority_queue< ListNode*,vector<ListNode*>,function<bool(ListNode*,ListNode*)>> que([] (ListNode* l1,ListNode* l2){return l1->val>l2->val;});
        
        for(ListNode* head:lists){
            if(head!=nullptr)
                que.push(head);
        }
        while(!que.empty()){
            ListNode* temp=que.top();
            que.pop();
            p->next=temp;
            p=p->next;
            if(temp->next!=nullptr)
                que.push(temp->next);
        }
        return dummy->next;
    }
};

这种自定义方式参考的labuladong<双指针技巧>

2.创建一个结构体,重载()符号

class Solution {
public:
    class cmp{//自定义数据类型
    public:
        bool operator()(ListNode* l1,ListNode* l2){
            return l1->val>l2->val;
        }
    };
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int k=lists.size();
        ListNode* dummy=new ListNode(0);
        ListNode* p=dummy;
        priority_queue< ListNode*,vector<ListNode*>,cmp> que;
        for(ListNode* head:lists){
            if(head!=nullptr)
                que.push(head);
        }
        while(!que.empty()){
            ListNode* temp=que.top();
            que.pop();
            p->next=temp;
            p=p->next;
            if(temp->next!=nullptr)
                que.push(temp->next);
        }
        return dummy->next;
    }
};

这种自定义方式参考的代码随想录<栈与队列8.>

3.使用function.自定义比较函数

class Solution {
public:
    static bool Mycmp(ListNode* l1,ListNode* l2){ //注意需要加static
        return l1->val>l2->val;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        int k=lists.size();
        ListNode* dummy=new ListNode(0);
        ListNode* p=dummy;
        priority_queue< ListNode*,vector<ListNode*>,function<bool(ListNode*,ListNode*)>> que(Mycmp);
        for(ListNode* head:lists){
            if(head!=nullptr)
                que.push(head);
        }
        while(!que.empty()){
            ListNode* temp=que.top();
            que.pop();
            p->next=temp;
            p=p->next;
            if(temp->next!=nullptr)
                que.push(temp->next);
        }
        return dummy->next;
    }
};

注意:要在自己定义的Mycmp函数前加上static,不然会遇到如下错误:

在这里插入图片描述

此错误显示:必须调用对非静态成员函数的引用

解决办法: 在Mycmp函数的返回类型前加上static。
function.的定义方式参考的这里

四、拓展

另外还用到大小顶堆的有Leetcode347.前 K 个高频元素

1.题目描述

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
示例描述

2.思路

同样采用小顶堆的方式,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。

3.代码

具体代码及相关思路讲解如下:(时间复杂度为nlogk)

class Solution {
public:
    class cmp{//注意是class类型,struct也可以
    public:
        bool operator()(const pair<int,int> l,const pair<int,int> r){
            return l.second>r.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>map;
        for(int num:nums){
            map[num]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,cmp>que;//注意vector里面的元素也是pair<int,int>
        for(unordered_map<int,int>::iterator it=map.begin();it!=map.end();it++){
            que.push(*it);//注意是*it
            if(que.size()>k){//在队列中只维持k个元素,当超出k个元素时就把出现频率低的元素弹出
                que.pop();
            }
        }
        vector<int>res(k);
        for(int i=k-1;i>=0;i--){
            res[i]=que.top().first;
            que.pop();
        }
        return res;
    }
};

最后是按照出现频率由高到低的顺序进行输出的。


五、附上面试时的算法题

有N个链表均为升序排列方式,找出这N个链表中第k小的元素。
例:现有3个链表,找出第4小的元素。
{2,3,4,5,7}{3,5,9,11,14}{1,6,7,8},第4小的元素为3

思路:也是采用优先队列的方式,队列中只需维护N个链表中一个节点即可,依次找到

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

class cmp{
public:      //注意!!别忘了写public
    bool operator()(ListNode* l1,ListNode* l2){
        return l1->val>l2->val;
    }
};

class Solution{
public:
    int mergeKLists(vector<ListNode*>& lists,int k) {
        priority_queue<ListNode*,vector<ListNode*>,cmp> que; //注意第二个参数代表容器类型
        for(auto head:lists){
            if(head!=nullptr)  //注意!
                que.push(head);
        }
        ListNode* cur;
        while(k){
            ListNode* temp=que.top();
            que.pop();
            k--;
            que.push(temp->next);
            cur=temp;
        }
        return cur->val;

    }
};
int main(){
    vector<ListNode*> vec;
    ListNode* dummy1=new ListNode(0);
    ListNode* l1=dummy1;
    l1->next=new ListNode(2);
    l1=l1->next;
    l1->next=new ListNode(3);
    l1=l1->next;
    l1->next=new ListNode(4);
    vec.push_back(dummy1->next);
    ListNode* dummy2=new ListNode(0);
    ListNode* l2=dummy2;
    l2->next=new ListNode(3);
    l2=l2->next;
    l2->next=new ListNode(5);
    l2=l2->next;
    l2->next=new ListNode(9);
    vec.push_back(dummy2->next);
    ListNode* dummy3=new ListNode(0);
    ListNode* l3=dummy3;
    l3->next=new ListNode(1);
    l3=l3->next;
    l3->next=new ListNode(6);
    l3=l3->next;
    l3->next=new ListNode(7);
    vec.push_back(dummy3->next);
    int k=4;
    Solution s;
    int result=s.mergeKLists(vec,k);
    cout<<result<<endl;

    return 0;
}

或者直接采用lambda表达式的形式自定义比较函数:

priority_queue<ListNode*,vector<ListNode*>,function<bool(ListNode*,ListNode*)>> 
        que([](ListNode* l1,ListNode* l2){return l1->val>l2->val;}); //注意第二个参数代表容器类型

运行结果:
在这里插入图片描述

总结

以上就是用到小顶堆的两道题目,分别为链表和数组类型。概括了一下比较函数不同的自定义方式。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值