单调队列

什么是单调队列

单调队列,即单调递减或单调递增的队列。使用频率不高,但在有些程序中会有非同寻常的作用。

单调队列的作用

不断地向缓存数组里读入元素,也不时地去掉最老的元素,不定期的询问当前缓存数组里的最小/最大的元素。
最直接的方法:普通队列实现缓存数组。
用堆实现缓存数组

运行耗时

堆顶始终是最小/最大元素,进队出队都是O(1)。
一次查询需要遍历当前队列的所有元素,故O(n)。
而进队出队,都要调整堆,是O(log(n))。

操作

进队时,将进队的元素为e,从队尾往前扫描,直到找到一个不大于e的元素d,将e放在d之后,舍弃e之后的所有元素;如果没有找到这样一个d(即已入队的元素全部比e大),则将e放在队头(此时队列里只有这一个元素)。

出队时,将出队的元素为e,从队头向后扫描,直到找到一个元素f比e后进队,舍弃f之前所有的。(实际操作中,由于是按序逐个出队,所以每次只需要取队头元素出队即可)。

每个元素最多进队一次,出队一次。

模板题

题目描述

给一个数组,该数组中有n个元素,要求找出每连续的m个数中的最大值和最小值。(即找出每个连续m个区间长度中的最大值和最小值。)

输入

有一组测试样例。
第一行有两个数n和m,n代表数组的长度,m代表连续区间的长度。
第二行输入n个数,表示该数组中的n个元素。

输出

每连续的m个数中的最大值和最小值。

解题思路

这个是典型的固定m区间的单调队列。套用的本质思想是:
如求最小值: 考虑这样的一个问题,在某个区间当中如果存在某两个元素A,B,满足A的下标小于B的下标,A的值大于B的值,那么A这个数就可以删掉,不再考虑。

例如有数列       1 5 2 3 4
很显然   5的下标为 2, 2的下标为3     2<3,但 5>2  不符合单调递增
故删去5,数列成为         1 2 3 4            符合单调递增

如求最大值: 考虑这样的一个问题,在某个区间当中如果存在某两个元素A,B,满足A的下标小于B的下标,且A的值小于B的值,那么A这个数就可以删掉,不再考虑。

例如有数列       6 4 5 2 1
很显然   4的下标为 2, 5的下标为3     2<3,且 4<5  不符合单调递减
故删去4,数列成为         6 5 2 1            符合单调递减

具体的操作是:从加入第m个数开始,每插入做一次队列单调性更新:
但是可能会遇到正阳一种情况:

例如有数列       1  5  4  6  9  10. 要求m值为3
整理后得到       1  4  6  9  10
第一个连续m区间[1,3]时 单调队列v存的是(存下标) 1 3.
    故最小值是队首a[v[1]]=a[1]=1
----------
第二个连续m区间[2,4]时 单调队列v存的是(存下标) 1 3 4.
    此时队首依然是a[v[1]],但很显然v[1]已经不再区间[2,4]中了.
    故此时我们需要把队首后推一位,直至在区间[2,4]内即a[v[2]]=a[3]=4

主要步骤就是

删队尾【单调性】,入队,删队首【下标范围m以内】,输出队首【即最值】。
代码(即单调队列模板)
#include <iostream>
#include<cstdio>
using namespace std;
const int Max = 1000004;
int n,m,a[Max];//定义输入的三个量
int v[Max],h,t;
//数组v代表单调队列,存放数组a的下标,h代表队首,t代表队尾
void getmin()//单调递增
{
    h = 1;t = 0;//代表队列的头和尾

    for(int i = 1; i <= n; i++)
    {
        while(h <= t && a[v[t]] >= a[i]) t--;
        //如果要插入的数比队尾的数小,将对位的取代
        v[++t] = i;
        if(i>=m) //前m个数一定是在第一个m区间的
        {
            while(v[h] < i-m+1) h++;//此时的最小值不再m区间内,队首后移
            printf("%d ",a[v[h]]);//输出队首元素,即最小值
        }
    }
    cout << endl;
}
void getmax()//单调递减
{
    h = 1;t = 0;
    for(int i = 1; i < m; i++)
    {
        while(h <= t && a[v[t]] <= a[i]) t--;
        v[++t] = i;
    }
    for(int i = m; i <= n; i++)
    {
        while(h <= t && a[v[t]] <= a[i]) t--;
        v[++t] = i;
        if(i>=m) //前m个数一定是在第一个m区间的
        {
             while(v[h] < i-m+1) h++;
             printf("%d ",a[v[h]]);
        }
    }
    cout << endl;
}
int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    getmin();
    getmax();
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值