2823 :Sliding Window:滑动窗口问题:单调队列原理+模板+使用~

题目

题目链接
滑动窗口求解数组最大最小值
在这里插入图片描述

思路分析

RMQ和线段树显然可以做,但是线段树打起来太麻烦了,学点简单的方法总是好的。

单调队列是一种队列(废话)

其中队列的元素保证是单调递增或者是单调递减的

那么队首的元素不就是最小(或最大)的吗?

很明显,当我们在计算区间 [ i − k + 1 , i ] [i-k+1,i] [ik+1,i] 的最大值时,是不

是区间 [ i − k + 1 , i − 1 ] [i-k+1,i-1] [ik+1,i1]我们之前已经计算过了?那么我们是不是要保存上一次的结果呢(主要是最大值)?

这时,单调队列登场——

单调队列主要有两个操作:删头和去尾

1.删头

如果队列头的元素离开了我们当前操作的区间,那么这个元素就没有任何用了,我们就要把它删掉,也就是说删头是为了去除过期的数据。

2.去尾

假设即将进入队列的元素为 X X X ,队列尾指针为 t a i l tail tail

这时我们要比较二者的大小

  • X ≤ q [ t a i l ] X\leq q[tail] Xq[tail]:此时q仍然保证着递减性,故直接将 X 插入队列尾
  • X > = q [ t a i l ] X>=q[tail] X>=q[tail]:此时,队列递减性被打破,此时我们做一下操作:
    ① 弹出队尾元素:因为当前的 q [ t a i l ] q[tail] q[tail] 不但不是最大值,对于以后的情况也不可能比 X X X更优。所以要弹出这好比当前队列尾部的元素是个能力不足的老兵,而新加入的新兵更年轻,更能打,当然不要老兵了
    ②重复执行①,直到满足 X < q [ t a i l ] X<q[tail] X<q[tail] 或者队列为空为止
    ③将 XX 插入队列

对于样例而言:

[1 3 -1] -3 5 3 6 7 
q={1},{3},{3,-1} output:3//分别为每次操作的结果
1 [3 -1 -3] 5 3 6 7 
q={3,-1,-3} output:3
1 3 [-1 -3 5] 3 6 7
q={-1,-3},{-1,5},{5} output:5
1 3 -1 [-3 5 3] 6 7
q={5,3} output:5
1 3 -1 -3 [5 3 6] 7 
q={5,6},{6} output:6
1 3 -1 -3 5 [3 6 7]
q={7} output:7

由于每个元素最多入队一次,出队一次(为什么?),所以时间复杂度为 O ( n ) O(n) O(n)当然,这题还可以用ST表和线段树来做,但都没有单调队列方便

实现:
由于要对队首和队尾进行维护,所以我们需要使用

双端队列

可以用STL中的deque,也可以手写

2 d.front():返回的一个元素的引用。
3 d.back():返回最后一个元素的引用。
4 d.pop_back():删除尾部的元素。不返回值。
5 d.pop_front():删除头部元素。不返回值。
6 d.push_back(e):在队尾添加一个元素e。
7 d.push_front(e):在对头添加一个元素e。

方便起见,我们在单调队列中维护数组的下标,省去了使用pair或者结构体的繁琐:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<deque>
#include<vector>
#include<algorithm>
using namespace std;
int n,m;
int a[1000001];
int b[1000001];
int c[1000001];
deque<int> qx,qn;
int cnt;
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        while(!qx.empty()) qx.pop_front();
        while(!qn.empty()) qn.pop_front();
        for(int i=0;i<n;i++)
        scanf("%d",&a[i]);
        for(int i=0;i<n;i++)
        {
            while(!qx.empty()&&a[i]>=a[qx.back()])//判断是否新加得数比前面得大,大的话我们就要把最大值放最前,
                 qx.pop_back();
            qx.push_back(i);
            while(!qn.empty()&&a[i]<=a[qn.back()])
                 qn.pop_back();
            qn.push_back(i);
            if(i>=m-1)
            {
                while(!qx.empty()&&qx.front()<=i-m) qx.pop_front();//我们把出了区间的数踢出队列
                b[cnt]=a[qx.front()];
                while(!qn.empty()&&qn.front()<=i-m) qn.pop_front();
                c[cnt++]=a[qn.front()];
            }
        }
        for(int i=0;i<cnt;i++)
        {
            if(i==0)
            printf("%d",c[i]);
            else printf(" %d",c[i]);
        }
        printf("\n");
        for(int i=0;i<cnt;i++)
        {
            if(i==0)
            printf("%d",b[i]);
            else printf(" %d",b[i]);
        }
        printf("\n");
    }


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值