代码随想录 | Day14 | 栈和队列:滑动窗口最大值&&前K个高频元素

代码随想录 | Day14 | 栈和队列:滑动窗口最大值&&前K个高频元素

主要学习内容:

1.使用单调队列解决滑动窗口

2.使用优先级队列

3.map的第二个元素的排序问题

239.滑动窗口最大值

239. 滑动窗口最大值 - 力扣(LeetCode)

思路:

使用单调队列维护一个单调递减的队列,具体看这个例子

举例子来说明:

nums={2,1,4,2,3,2},k=3

image-20240602142731714

假设你在坐飞机,你的面前是要飞越的山,数组就是它们的高度,你的视野范围即你能看到的山的数量是滑动窗口大小,题目要求是,你想要找到视野范围内最高的山并且记录下来

单调队列的模拟过程

  1. 首先是 2 1进入你的眼帘,我们把2 1入队

  2. 当4进入你的视野后,2和1就再也不可能是你视野中最高的山了

所以我们在 2 1 4中删去 2 1留下4,现在队中只剩下4,所以你在第一块视野内最高的山峰是4,记录下来

  1. 接着飞机飞到了2

2有可能成为你视野中的最大值,因为4可能会离开你的视野,即滑动窗口内已经不包含4了,所以入队

此时4仍然是视野内最高的山峰,记录下来

  1. 接着飞到了 3

再遇到3时就和2 1 遇到4一样,这里的2也不可能成为你视野中的最高的山峰了

所以删除2 此时队列剩下的是 4 3,4仍然是你视野中最高的山峰,记录下来

  1. 接着飞到了 2

2比3小,有可能成为你视野中最高的山峰,入队

注意,当飞机飞到了2,你的视野中就不包含4了,因为你的视野只能有3座山 此时是2 3 2,第一个2已经在第4步被删掉了

4不在视野内当然要删掉了,所以队列内只剩下3 2

最高的山峰就是3,记录下来,遍历到了数组末尾,遍历结束

模拟结束我们可以回答这样的问题

为什么要用单调队列?

因为要让队首元素成为我们要的最大值,它前面的比它小的在我们的队列中没有必要维护,因为它们不可能成为视野内最高的山峰就像4前面的 2 1一样

就像是4 2 3一样,没有遇到3时,2还有可能是最大值,遇到3之后2再也不可能成为最大值,所以要删去2留下 4 3保持队列单调递减,这样就只留下了最大值和接下来有可能成为最大值的山峰

代码实现

接下来的任务就是代码实现我们的模拟过程,最好用单调队列记录对应元素的下标而不是这个元素,具体原因可以看下面

分为4步

1.删除元素,让队列保持单调递减

//如果队列不为空并且当前的山峰高于队列末尾的山峰 就一直弹出,直到队列末尾元素大于等于当前山峰,保持了队列单调递减

//2 1 4 (此时4还没有进入队列)
//4>1 弹出1 4大于2 弹出2

//4 2 3
//3>2 弹出2 3<4 结束删除元素的过程
while(!q.empty()&&nums[i]>nums[q.back()])
                q.pop_back();

2.元素入队

q.push_back(i);

3.检查队首元素是否已经滑出窗口

//当前元素和队首元素下标之间距离大于滑动窗口大小
if(i-q[0]+1>k)
                q.pop_front();

4.记录答案

if(i>=k-1)
            res.push_back(nums[q.front()]);

完整代码:

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> res;
        for(int i=0;i<nums.size();i++)
        {
            while(!q.empty()&&nums[i]>nums[q.back()])
                q.pop_back();
            q.push_back(i);
            if(i-q[0]+1>k)
                q.pop_front();
            if(i>=k-1)
                res.push_back(nums[q.front()]);
        }
        return res;
    }
};

单调队列记录对应元素的下标而不是这个元素原因

下面是我自己写的代码,很明显不对

我是用队列存储元素,对应上面的四个步骤

1.删除操作

if里面删除的是 2 1 4这种情况

else 里面删除的是 4 2 3的2这种情况

2.入队

删完了直接入队

3.检查队列大小

我是直接用队列大小来检查队首是否滑出窗口,这显然不对

比如 4 2 3这种情况

已经把2删了只剩下 4 3当然队列大小当然比3小了,但是4已经不是窗口内了,所以导致了错误

4.记录答案

所以问题最大的就是检查队首有没有滑出队列

但是这个时候就发现,除了用队列大小也没其他方法,因为我们不知道队列首部那个元素的下标,也没法计算当前元素到队首的元素的距离,所以最好用队列存储元素的下标而不是相应的元素

其次的话两个删除操作可以二合一,这个可以改进

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        deque<int> q;
        vector<int> res;
        for(int i=0;i<nums.size();i++)
        {
            if(!q.empty()&&nums[i]>q.front())
            {
                while(!q.empty())
                    q.pop_front();
                q.push_back(nums[i]);
            }
            else
            {
                while(!q.empty()&&nums[i]>q.back())
                    q.pop_back();
                q.push_back(nums[i]);  
            }
            while(q.size()>k)
                    q.pop_front();
            if(i>=k-1)
                res.push_back(q.front());
        }
        return res;
    }
};

单调队列 滑动窗口最大值【基础算法精讲 27】_哔哩哔哩_bilibili

参考的灵神的视频,大家可以看看

347.前K个高频元素

347. 前 K 个高频元素 - 力扣(LeetCode)

解法1:哈希+排序

很明显用map,key就是nums[i],val就是出现次数

然后根据val排序,输出前k个元素即可

需要注意的是自己写排序函数和把map转换成vector才能排序,没什么难点

class Solution {
public:
    bool static cmp(pair<int,int> a,pair<int,int> b)
    {
        return a.second>b.second;
    }
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int,int> p;
        vector<int> res;
        for(auto c:nums)
            p[c]++;
        vector<pair<int,int>> v(p.begin(),p.end());
        sort(v.begin(),v.end(),cmp);
        for(int i=0;i<k;i++)
            res.push_back(v[i].first);
        return res;
    }
};

解法2:优先级队列

首先要解决使用大顶堆还是小顶堆的问题

如果使用大顶堆,大的排在前面,在我们要加入新的元素的时候要进行弹出元素,那么大的元素就被弹出了,所以到最后我们求出来的其实是前K小的元素,而不是前K大的元素,所以要用小顶堆

思路:

先用map统计出现的频率,然后将map的元素pair对加入小顶堆,如果小顶堆的元素数量超过了k那就弹出一个元素,弹出去的一定是元素出现频率更小的元素,到最后小顶堆中只剩下前K个高频元素,把高频元素输出即可

class Solution {
public:
    class cmp {
    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> p;
        vector<int> res;
        for(auto c:nums)
            p[c]++;
        priority_queue<pair<int,int>,vector<pair<int,int>>,cmp> q;
        for(auto c:p)
        {
            q.push(c);
            if(q.size()>k)
                q.pop();
        }
        while(k--)
        {
            res.push_back(q.top().first);
            q.pop();
        }
        return res;
    }
};

个人觉得不如就用哈希加map排序…

这个是对前K个进行排序,哈希是全部的进行排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值