算法训练营DAY13|239. 滑动窗口最大值、347.前 K 个高频元素

这一期是栈与队列应用的尾声了,这一期实际上是对于队列的系列应用题,整体来看略有难度。


239. 滑动窗口最大值 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/sliding-window-maximum/这道题是一道困难题,给出一个滑动窗口的长度,给出一个数组,每次滑动窗口向数组右侧滑动一个元素,要求返回一个数组该数组记录的是每次移动滑动窗口所包含的数字中最大的一个分别是什么。

不难想出暴力解法,每移动一次遍历一遍窗口记录下来当前最大的数,时间复杂度是n*k,一共n个数据,要遍历k次。那么有没有更好的方法来节省时间呢?这时我们的单调队列要登场了!什么是单调队列?单调队列是单调递增的队列或者单调递减的队列,但是它和优先级队列的不同之处在于,我们设计它的时候主要是要维护大值或者小值而单调递增或递减的排序是进出元素控制的并非主要思路是排序,这和优先级队列的区别还是很大的,顺便说一下单调队列通常使用双端队列做模板实现,而优先级队列底层实现是堆,也就是二叉树。本题我们可以借助双端队列创建一个单调队列,因为c++里没有现成的单调队列,单调队列只需要三个函数,一个push一个pop一个求最大值,我们如何每次都弹出最大值呢?其实只是需要我们将最大值调整到队列口处,而前面小的数直接pop走,其实我们要求的是每次窗口中的最大值,所以小数完全没有必要维护,直接判断弹出即可,push时我们判断队尾有没有数小于要push进来的数,如果有全部pop出来,因为是在队尾插入所以要对对尾元素做判断,在实现pop函数时判断本次要删除的元素队头有没有没有就不删(没有的原因是之前push元素时候已经弹出了,因为为了保证取最大值的函数每次都取最大)一开始直接push前k个元素,之后采取push一个元素pop一个元素的规律,值得注意的是不是每一次都要pop,但是每一次都要调用,至于具体这一步是否真的pop了在pop函数会自动判断。有很多人看到这里会很懵,为什么会这样呢?原因在于我们设计的单调函数里实现的时候我们需要照顾到弹出最大值这个函数,所以我们设计成这样用来方便最大值的弹出,而在真正函数调用时候,为了保证逻辑上的正确,我们每次都要调用pop这个函数,判断如果此时我们要删的元素不在队头部,那么直接往后走加入新元素即可。

class Solution {
private:
class myqueue{
public:
        deque<int> dq;
    void push(int value){
        while(dq.empty()!=true&&value>dq.back()){
           dq.pop_back();
        }
        dq.push_back(value);
    }
    void pop(int value){
        if(dq.empty()!=true&&value==dq.front()){
            dq.pop_front();
        }
    }
    int getmax(){
        return dq.front();
    }
};
public:

    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int>result;
        myqueue mq;
        for(int i=0;i<k;i++)
        mq.push(nums[i]);
        result.push_back(mq.getmax());
        for(int i=k;i<nums.size();i++){
            mq.pop(nums[i-k]);
            mq.push(nums[i]);
            result.push_back(mq.getmax()); 
        }
        return result;
    }
};

347. 前 K 个高频元素 - 力扣(LeetCode)icon-default.png?t=M85Bhttps://leetcode.cn/problems/top-k-frequent-elements/这道题是考察优先级队列和语法理解的(我个人语法不是很强所以感觉代码有点难写)

这道题是一个输出前k个高频元素的题,并不是只输出一个。所以我们理所当然的应该想到用优先级队列来存取前k个高频元素,那么如何判断哪些是高频元素呢,我们用unordered_map来存取每个数据出现的次数,然后用小顶堆来存取数据。为什么要用小顶堆而不是大顶堆?我们的思路是这样的定义一个只能存去k个元素的堆,这样我们只需要维护k个数据,减少不必要的浪费。由于哈希存储结果是unordered_map来提高效率,所以map里是无序的,我们在插入元素时候要按着map依次插入进去,而用大顶堆的话,大的数据排在前面如果map里还有更大的数据,那么之前最大的数据要先被弹出来,往复操作最后只剩下频率最低的k个元素。而使用小顶堆那么每次弹出最小元素,加进来较大元素,最后沉淀下来的是k个高频数据。当然如果使用的是map的话可能就不需要考虑这些问题,因为是排好序的。

按着刚才的思路,给出代码

class Solution {
public:
    class compare{
        public:
        bool operator()(const pair<int,int>& a,const pair<int,int> & b){
            return a.second>b.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int>hash_map;
        for(int i=0;i<nums.size();i++){
            hash_map[nums[i]]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,compare> pq;
        for(unordered_map<int,int>::iterator it=hash_map.begin();it!=hash_map.end();it++){
            pq.push(*it);
            if(pq.size()>k)
            pq.pop();
        }
        vector<int>result(k);
        for(int i=k-1;i>=0;i--){
            result[i]=pq.top().first;
            pq.pop();
        }
        return result;
    }
};

需要注意的是要把map的两个数据都放到堆里,然后根据二元组的第二个关键词来进行排序,这样我们在进行最后一步转化为数组的时候,才能根据频率找到对应的数据,缺一不可!


以上代码均可ac。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值