菜狗复习系列:(3)单调队列

文章介绍了单调栈和单调队列的概念和应用场景。单调栈常用于处理序列中依赖单调性质的问题,例如找到序列中每个元素左边比它小的最近元素。而单调队列是在单调栈基础上的扩展,适用于滑动窗口最大值等问题,能在O(1)时间内获取区间最值。文章通过代码示例展示了如何实现这两种数据结构并解决相关问题。

什么情况下我们需要单调栈?

单调栈适合处理依赖序列单调性质的问题,

单调栈在某种意义上是在线算法,因为是动态增删元素。

给定一个序列,在该序列中左边比它小且离它最近的数在哪?

具体应用:单调栈

暴力做法:

for(int i=0;i<n;i++)
    for(int j=i;j>=0;j--)
        if(a[j]<a[i]) cout<

单调栈怎么实现?

优化思路:

如果存在a[x]>=a[y]且x<y

则a[x]显然不是y左边第一个比它小的数,出栈

依此规则,我们将获得一个严格单调的栈

栈顶即是y对应的答案,离y最近且比y小

如果不单调,我们将破坏单调性的元素

(没用的元素,不可能输出的元素,既较远又较大的元素)出栈

#include<iostream>
using namespace std;

const int N=100010;

int stk[N],tt=0;

int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int x;
        cin>>x;
        while(tt&stk[tt]>=x) tt--;//该循环使整个栈内元素单调递增
        if(!tt) printf("-1");
        else printf("%d",stk[tt]);
        stk[tt++]=x;
    }    
    return 0;
}

单调栈例题:直方图最大矩形的面积

解题思路:

枚举某一根柱子,将其高度固定为矩形的高度h,随后两边延伸,寻找高度比h小的柱子,也就是矩形的边界,当左右两边界宽度为w,那么对应的矩形面积为 w * h。

优化思路:关键在于找到左右边界的柱子

也就是离该柱子最近且较小的柱子

所以我们可以根据栈先进后出的特性,遍历存储有可能成为边界的柱子的下标,形成一个自底向上递增的单调栈

什么情况下我们需要单调队列?

基于单调栈的一个扩展,当单调栈的栈底元素可以弹出的时候,单调栈即可转化单调队列。 单调队列可以在 O ( 1 ) 的时间内,获取队首队尾区间的最值,因此单调的区间的最大用处,也在于获取区间的最值上。

具体应用:滑动窗口(经典模型)

单调队列怎么写?

解决思路:

窗口采用队列来维护

每次操作包括入队和出队

暴力做法:遍历队列(窗口)内的所有元素

优化思路:类似单调栈

寻找队列(窗口)中没有用的元素

用数组模拟的队列比stl更快

#include<iostream>
using namespace std;

const int N=1000010;

int q[N],a[N];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    
    int hh=0,tt=-1;
    for(int i=0;i<n;i++)
    {
        if(i-k+1>q[hh]) hh++;//队头向后移动一位
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;//使队列递增
        q[++tt]=i;
        if(i+1>=k) printf("%d ",a[q[hh]]);//输出队头,即最小值 
    }
    cout<<endl;
    hh=0,tt=-1;
    for(int i=0;i<n;i++)
    {
        if(i-k+1>q[hh]) hh++;
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;
        q[++tt]=i;
        if(i+1>=k) printf("%d ",a[q[hh]]); 
     } 
     return 0;
}

单调队列例题:滑动窗口的最大值

  1. 维护窗口的长队,队头是否需要出队

  1. 维护队列的单调性,队尾是否需要出队

  1. 输出队头,即最值

class Solution {
public:
    vector<int> maxInWindows(vector<int>& nums, int k) {
        vector<int>res;
        deque<int>q;
        for(int i = 0; i < nums.size(); i++){
            if(!q.empty() && i-q.front() >= k)//判断队头是否需要出队
                q.pop_front();
            while(!q.empty()&&nums[q.back()]<nums[i])//维护队列单调性
                q.pop_back();
            q.push_back(i);
            if(i >= k-1){
                res.push_back(nums[q.front()]);//取队头作为窗口最大元素
            }
        }
        return res;
    }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

社恐不参团

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

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

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

打赏作者

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

抵扣说明:

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

余额充值