优先队列(priority_queue)的妙用(以力扣周赛“K 次增加后的最大乘积”为例)

1.priority_queue的用法

优先队列在实际的动态更新过程中帮我们找到最大或者最小的元素,priority_queue的使用需要先包含头文件<queue>,即#include <queue>

priority_queue的本质是队列,拥有队列的一切操作,区别是在队列的基础上增添了内部的排序操作,这为我们在循环中动态更新最小值或者最大值提供了极大的便利,优先队列的操作如下:

  • top() 访问优先队列首元素
  • empty() 优先队列是否为空
  • size() 返回优先队列元素个数
  • push() 插入元素到优先队列队尾
  • emplace() 原地构造一个元素并插入优先队列
  • pop() 弹出优先队列队首元素
  • swap() 交换元素

在使用优先队列时,我们需要先进行定义,根据定义方式的不同,优先队列可以定义为大顶堆和小顶堆,所谓大顶堆的意思就是优先队列的队首元素是整个队列中最大的,其后元素按照从大到小进行排列,小顶堆则相反。

根据实际过程需要,我们希望队首元素为最大元素或者最小元素,那么如何来定义大顶堆或者小顶堆呢?

首先看优先队列的数据类型priority_queue<Type, Container, Functional>,其中Type 为数据类型,Container 为容器类型,Functional 为比较的方式,一般用反函数,需要说明的是Container必须是用数组实现的容器,比如vector,deque等,但不能用 list,默认情况下使用vector,使用基本数据类型时,只需要传入数据类型,默认为大顶堆,下面是int类型的使用举例:

// 优先队列的大顶堆定义方式
priority_queue<int> pq; //默认情况下使用为大顶堆
priority_queue<int,vector<int>,less<int>> pq; // 定义为大顶堆
// 优先队列的小顶堆定义方式
priority_queue<int,vector<int>,greater<int>> pq; // 定义为小顶堆

当需要用自定义的数据类型时可以通过重载操作符的方式实现优先队列的自定义排序:

struct Status{
    int val;
    bool operator < (const Status& a)const{
        return a.val < val;
    } 
};

priority_queue<Status> pq; // 小顶堆

2.例题分析与应用

下面为力扣第288场周赛第三题,前面两题相对来说能够直接用暴力解法解开,后面一题对现在的我来说又比较难(哭了°(°ˊДˋ°) ° ),正好这一道题不算难,也符合今天优先队列的主题,所以拿出来讲一讲。

 分析题目的意思和示例,其实我们能够很好的推出来,在有限的k次+1操作内,要使得最后得到的乘积最大,我们要做的肯定是,每一次操作后的数组中取出最小的元素x_min,+1操作后放回数组中去,为什么呢?不妨反过来想一下,这里记总的元素乘积为M,如果我们拿的不是最小的元素,记该元素为x,+1操作后最后的乘积多个M/x,而如果拿的是最小的元素x_min,+1操作后最后的乘积会多个M/x_min,显然M/x_min > M/x,所以本题的唯一难点是如何在k次+1操作这样动态的过程中以最快的时间取出最小的元素+1后放回数组中。

如果不计时间的话,照这个逻辑,很通顺的能够想到先对数组进行排序,然后取出第一个元素,+1后不断与后面元素比较,直到找到比它大的元素,然后放在它前面,进入下一次循环,但是这种实现方式时间复杂度过高,这时我们就该考虑用到优先队列的方式。

使用优先队列我们可以采用小顶堆的方式,先将数据元素放入优先队列中,然后进行k次循环操作,不断用top()拿出最小的元素,+1操作后利用push()放回优先队列,然后调用pop()删除队首元素。k次循环结束后,我们将队列中的每个元素乘积取余即可。

class Solution {
    const int long_num = (pow(10,9)+7);//避免乘积过大取余用
public:
    int maximumProduct(vector<int>& nums, int k) {
        priority_queue<int,vector<int>,greater<int>> pq;// 利用greater仿函数建立小顶堆
        for(auto num : nums) pq.push(num); // 往小顶堆放入元素

        int index = 0; // 指示用,表示进行k次操作
        while(index < k){
            int temp_num = pq.top(); // 取优先队列中的最小元素
            pq.pop(); // 删除队列首元素,也就是原先队列中最小的元素
            ++temp_num; // 最小元素加1
            ++index; // 操作数加1
            pq.push(temp_num); // 加增加后的元素重新放入优先队列中进行排序
        }

        long long ans = 1; // 乘积初始值
        while(!pq.empty()){
            int temp_multi = pq.top(); // 取优先队列中的队首元素
            pq.pop(); // 删除队列首元素
            ans *= temp_multi; // 计算乘积结果
            ans %= long_num; // 取余,避免乘积值过大
        }
        return ans; // 返回计算结果
    }
};

当然,也可以用默认大顶堆的方式来实现,只需要在放入元素时取其负数放入,k次循环中+1操作变为-1操作即可。

class Solution {
    const int long_num = (pow(10,9)+7);//避免乘积过大取余用
public:
    int maximumProduct(vector<int>& nums, int k) {
        priority_queue<int> pq;// 默认建立大顶堆
        for(auto num : nums) pq.push(-num); // 往大顶堆放入元素

        int index = 0; // 指示用,表示进行k次操作
        while(index < k){
            int temp_num = pq.top(); // 取优先队列中的最大元素
            pq.pop(); // 删除队列首元素,也就是原先队列中最大的元素
            --temp_num; // 最小元素减1
            ++index; // 操作数加1
            pq.push(temp_num); // 加增加后的元素重新放入优先队列中进行排序
        }

        long long ans = 1; // 乘积初始值
        while(!pq.empty()){
            int temp_multi = pq.top(); // 取优先队列中的队首元素
            pq.pop(); // 删除队列首元素
            ans *= (-temp_multi); // 计算乘积结果
            ans %= long_num; // 取余,避免乘积值过大
        }
        return ans; // 返回计算结果
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值