zoukankan      html  css  js  c++  java
  • 【题解】滑动窗口

    为了解决滑动窗口,我们引入单调队列的概念。

    分析题目的要求,我们需要建立一种数据结构,可以满足以下要求:

    • 可以快速读取一个区间的最大值和最小值

    • 能根据编号的大小将元素快速弹出

    先分析最大值。对于上述要求,我们可以用一个单调队列来解决这个问题。

    我们不妨先看一组测试数据。

    8 3
    1 3 -1 -3 5 3 6 7
    

    滑动窗口的运动轨迹如下:

    1] 3 -1 -3 5 3 6 7

    1 3 ] -1 -3 5 3 6 7

    [1 3 -1] -3 5 3 6 7 此时滑动窗口已经完全进入了数列

    1 [3 -1 -3] 5 3 6 7

    1 3 [-1 -3 5] 3 6 7

    1 3 -1 [-3 5 3] 6 7

    1 3 -1 -3 [5 3 6] 7

    1 3 -1 -3 5 [3 6 7] 滑动窗口已经滑到了最右边

    我们可以用一个单调递减队列来解决这个问题——我们可以在队首取到最大值。

    我们用一个变量 (i) 来模拟窗口的右侧。任意一个时刻内,宽度为(m)的窗口,可以表示成一个运动的区间([i-m+1,i])。我们让(i)从1到n循环枚举,每一次,我们都对扫描到的元素进行判断,看其能否进入队列。注意,我们使用的是一个单调队列,队列里面的元素是单调递减的,这样我们就可以在对头取到最大值。如果当前元素比队尾的元素还要大,根据单调队列的定义,若把当前元素加入到队列中,那么原来队尾的元素就会处于一个低谷状态:它是不可能成为最大值的。原因很简单:队列里面的所有元素都会往队首跑,这个“低谷状态”的队列元素最终会到达队首,而它的前一号元素会比它大。这不符合我们“在队首取得最大值”的要求。这个“低谷元素”就没有存在的必要了。

        while(head<=tail && q[tail]<=a[i])
                        --tail;
                    q[++tail]=a[i];
                    p[tail]=i;//p表示队列对应元素的编号
    

    我们发现,这里单调队列的使用有一点点像“栈”。如果仅仅只是像这样子扫描,然后入队,我们还不如建立一个单调栈呢?

    其实不然。单调队列有一个特点,就是既可以从队首出队,又可以从队尾出队。我们除了考虑快速取得最值,还要考虑一点:由于是滑动窗口,有些窗口内的元素最终会运动到窗口外。所以,我们还要考虑队列里面的元素是否“过时”。

    由于我们是按照时间顺序将元素存入队列内,因此过时的元素更有可能出现在队首,因为队尾都是新鲜的元素。由于窗口的右边界是(i),我们只要判断队列元素的编号和窗口左边界的关系(i-m+1)就可以了。如果当前元素的编号(rank<i-m+1),即(rank<=i-m),我们就把它从队首弹出。

    while(p[head]<=i-m)
                        ++head;
    

    综上所述,我们可以用单调队列解决这个问题。分析最小值同理。

    #include<bits/stdc++.h>
    #define For(i,a,b) for(register int i=a;i<=b;i++)
    using namespace std;
    
    struct Mq{
        static const int nmax=1000001;
        int n,k,a[nmax];
        int q[nmax],head,tail,p[nmax];
    
        void read()
            {
                scanf("%d %d",&n,&k);
                for(register int i=1;i<=n;++i)
                    scanf("%d",&a[i]);
            }
        void Mmax()
            {
                head=1;
                tail=0;
                for(register int i=1;i<=n;++i)
                {
                    while(head<=tail && q[tail]<=a[i])
                        --tail;
                    q[++tail]=a[i];
                    p[tail]=i;
                    while(p[head]<=i-k)
                        ++head;
                    if(i>=k)printf("%d ",q[head]);
                }
                printf("
    ");
            }
        void Mmin()
            {
                head=1,tail=0;
                for(register int i=1;i<=n;++i)
                {
                    while(head<=tail && q[tail]>=a[i])
                        --tail;
                    q[++tail]=a[i];
                    p[tail]=i;
                    while(p[head]<=i-k)
                        ++head;
                    if(i>=k)
                        printf("%d ",q[head]);
                }
                printf("
    ");
            }
    }monotone_queue;
    
    int main()
    {
        monotone_queue.read();
        monotone_queue.Mmin();
        monotone_queue.Mmax();
        return 0;
    }
    

    总结一下单调队列的三部曲:

    • 判单调
    • 判过期
    • 更答案

    注意一下,这三个步骤的具体顺序还是要看题目的要求。注意在扫描的过程中,只有当当前新决策的收益或代价可以确定时,才能判定单调,且必须将其插入队列。
    建议根据上面的要求,在以下两种顺序中选一个:

    判单调( ightarrow)判过期( ightarrow)更答案

    判过期( ightarrow)更答案( ightarrow)判单调

  • 相关阅读:
    ThinkPHP中pathinfo模式与URL重写
    JS分割字符串并放入数组的函数
    js解决弹窗问题实现班级跳转DIV示例
    javascript客户端遍历控件与获取父容器对象
    Apache 虚拟目录和默认首页的设置
    linux Apache CGI 安装配置
    apache与和mysql重启命令
    带你从零学ReactNative开发跨平台App开发(三)
    带你从零学ReactNative开发跨平台App开发(一)
    C#/Net定时导出Excel并定时发送到邮箱
  • 原文地址:https://www.cnblogs.com/LinearODE/p/10152005.html
Copyright © 2011-2022 走看看