代码随想录算法训练营day13

文章介绍了在C++中如何使用优先队列(包括大顶堆和小顶堆)解决滑动窗口问题,如找到最大值和找出前K个高频元素。重点讲解了单调队列和小顶堆在优化时间复杂度上的应用。
摘要由CSDN通过智能技术生成

题目:239. 滑动窗口最大值、347.前 K 个高频元素

参考链接:代码随想录

基础知识

cpp中的优先队列,需要#include <queue>,本质是堆的实现,分为大顶堆和小顶堆。内置函数和queue类似,包括top,empty,size,push,emplace,pop,swap。

priority_queue<Type, Container, Functional>//定义,其中container是容器类型(vector或deque)
// Functional是比较方式
//升序队列,小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//降序队列,大顶堆
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)

建立的堆默认为大顶堆,即最大优先队列,每次出队的时候都会先出最大元素。容器类型默认为vector。
cpp中还有双向队列deque。可以向两端发展。接口如下:

deq.size();
deq.max_size();
deq.resize();
deq.empty();
deq.push_front(const T& x);
deq.push_back(const T& x);
deq.pop_front();
deq.pop_back();
deq.clear();
deq.front();
deq.back();
//还有许多复杂度O(n)的接口

239. 滑动窗口最大值

思路:本题首先想到的是暴力算法,时间复杂度O(n*k)。由于需要返回滑动窗口最大值,可以想到使用优先队列的结构。由于需要计算最大值,故将窗口中的元素放在一个大顶堆中,当窗口滑动时,逐渐计算最大值,但这样的问题是无法在窗口移动的同时将出窗口的元素剔除出去。
看了解析,需要用到一种叫做单调队列的结构。即队列里面始终单调递增或者单调递减。本题选择单调递减,即front()出口始终为窗口中最大元素。队列中不需要维护整个窗口,只需要维护可能成为最大值的元素。push()操作即当一个元素从back入队时,如果其大于目前队尾,则其可能成为最大值,此时将队尾出队,不停弹出,直到其小于等于队尾,此时满足单调递减队列。对pop(),如果目前窗口移除的元素等于top(),此时队首需要出队,其他时候不用操作。时间复杂度O(n)。

class Solution {
public: 
    class MyQueue{
    public:
        deque<int> deq;
        void push(int x){
            if(deq.empty()||x<=deq.back()){//一定要确保队列单调递减
                deq.push_back(x);
            }
            else{
                while(!deq.empty()&&x>deq.back()){
                    deq.pop_back();
                }
                
                deq.push_back(x);
            }
        }
        void pop(int x){//每次pop都是一个元素,代表窗口滑动
            if(!deq.empty()&&x==deq.front()){
                deq.pop_front();
            }
        }
        int front(){
            return deq.front();
        }
    };
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue pq;
        vector<int> ans;
        int i;
        
        for(i=0;i<k;i++){
            pq.push(nums[i]);
            // cout << "hello" << endl;
        }
        ans.push_back(pq.front());
        for(;i<nums.size();i++){
            pq.pop(nums[i-k]);
            pq.push(nums[i]);
            ans.push_back(pq.front());
        }
        return ans;
    }
};

写的时候一开始出了点错,首先是push的时候是和队尾作比较以保证单调递减,而不是队头。然后是while循环中必须确保队列不为空才可能比较front(),否则报错。标答的push操作比我更简单一点,只用一个while就搞定了。
标答:

class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        // 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
        // 同时pop之前判断队列当前是否为空。
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);

        }
        // 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
        int front() {
            return que.front();
        }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
            que.push(nums[i]);
        }
        result.push_back(que.front()); // result 记录前k的元素的最大值
        for (int i = k; i < nums.size(); i++) {
            que.pop(nums[i - k]); // 滑动窗口移除最前面元素
            que.push(nums[i]); // 滑动窗口前加入最后面的元素
            result.push_back(que.front()); // 记录对应的最大值
        }
        return result;
    }
};

347.前 K 个高频元素

思路:我一开始想的是使用一个unordered_map来记录每个元素的频次,然后使用大顶堆,将前k个频次记录下来,但是在寻找具体元素的时候遇到了麻烦,对每个元素都需要在map中遍历一次,时间复杂度较高。看了解析,发现需要使用小顶堆,因为大顶堆的实际上是需要将所有元素做一个排序,而小顶堆只要每次弹出最小元素,最后留下的k个元素就是最大的。而且小顶堆中需要存放一对pair<int,int>,方便找元素。时间复杂度O(nlogk)。

class Solution {
public:
    struct compare{//仿函数,这里写结构体和类都可以
        bool operator()(const pair<int,int> &p1,const pair<int,int> &p2){
            return p1.second>p2.second;//记住是左大于右是小顶堆,相当于greater
        }
    };
    
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> mp;
        for(int i:nums){//统计频率
            mp[i]++;
        }
        priority_queue<pair<int,int>,vector<pair<int,int>>,compare > pq;//小顶堆
        for(auto it=mp.begin();it!=mp.end();it++){
            pq.push(*it);
            if(pq.size()>k){//如果队列长度大于k,则出队最小元素,最后剩下的一定是k个最大
                pq.pop();
            }
        }
        vector<int> ans;//record用于记录k个最大频率
        while(!pq.empty()){
            ans.push_back(pq.top().first);
            pq.pop();
        }
        return ans;
    }
};

写的时候注意仿函数的写法。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值