单调队列 算法思想+模板

题目背景

给出一个长度为n的数组,编程输出每k个连续的数中的最大值和最小值。


思路

朴素暴力思路

单调队列 算法思想+模板_第1张图片

单调队列优化

要求的是每连续的k个数中的最小(最大)值,很明显,当一个数进入所要 “寻找” 最小值的范围中时,若这个数比其前面(先进队)的数要小,显然,前面的数会比这个数先出队且不再可能是最小值。
也就是说——当满足以上条件时,可将前面的数 “弹出”,再将该数真正 push 进队尾。
这就相当于维护了一个递增的队列,符合单调队列的定义,减少了重复的比较次数,不仅如此,由于维护出的队伍是查询范围内的且是递增的,队头必定是该查询区域内的最小值,因此输出时只需输出队头即可。
显而易见的是,在这样的算法中,每个数只要进队与出队各一次,因此时间复杂度被降到了O(N)
而由于查询区间长度是固定的,超出查询空间的值再小也不能输出,因此每次都需要判断一下对头元素是否已经不在滑动区间内了。


代码模板

#include
using namespace std;
const int N=1000010;
int a[N],q[N];      //注意,存在队列中的不是元素的值,而是元素的下标
int hh=0,tt=-1;
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&i-k+1>q[hh]) hh++;              //队列非空,且队头元素存的下标已经超出了窗口 出队 防止把超出滑动窗口的值输出出来,每次都要先判断下,已经不在当前窗口的值,再符合条件也不能输出
        while(hh<=tt&&a[q[tt]]>=a[i]) tt--;       //队列非空,且队尾中元素大于等于新要加入的元素,那么队尾元素怎么也不会输出的,直接给去了,去掉没有符合这种情况的为止。出这种循环有三种可能,一是队列空了,说明没有元素比新加入的元素还要小,它就是最小,下面一步相当于把它加到队头然后输出;另外一种情形是队列中即窗口中存在比新加入元素还要小的元素,那下面把新元素加进去,输出队首那个最小的
        q[++tt]=i;    //把新要加入的元素加进去
        //这句必须写在输出前面,因为很有可能把队列搞空了,没有输出了
        if(i>=k-1) printf("%d ",a[q[hh]]);   //至少得处理过k个元素才能输出第一个,因为确实是每k个元素找最值,没处理到k个的时候,不要输出
    }
    printf("\n");
    
    hh=0,tt=-1;
    for(int i=0;i<n;i++)
    {
        if(hh<=tt&i-k+1>q[hh]) hh++;              //队列非空,且队头元素存的下标已经超出了窗口 出队
        while(hh<=tt&&a[q[tt]]<=a[i]) tt--;       //队列非空,且队尾中元素小于等于新要加入的元素
        q[++tt]=i;    //把新要加入的元素加进去
        //这句必须写在输出前面,因为很有可能把队列搞空了,没有输出了
        if(i>=k-1) printf("%d ",a[q[hh]]);
    }
    return 0;
}

注意点请参考注释


主要应用

  • 滑动窗口(此题)
  • 动态规划时的状态优化

你可能感兴趣的:(#,基础算法模板总结,算法,数据结构,队列)