POJ 2823 (从经典滑动窗口最大值问题入门单调队列)

题目链接

POJ 2823

题目大意

输入一个长度为n(n 106 )的数列,给定一个长度为k的窗口,让这个窗口在数列上移动,求移动到每个位置窗口中包含数的最大值和最小值。即设序列为 A1 , A2 ,…, An ,设f(i)=min{ Aik+1 , Aik+2 ,…, Ak } ,g(i)=max{ Aik+1 , Aik+2 ,…, Ak }
求:f(k),f(k+1),…,f(n) g(k),g(k+1),…,g(n).

分析

本题算是单调队列的经典入门题。因此拿来学习单调队列。
如果单纯对每一个i都去扫描前面k个数去求最值,那复杂度为o(n*k),肯定超时。
如果用线段树或者树状数组维护区间最值,那复杂度为o(nlogn)。
但还有更简洁更高效的办法,即单调队列。//可参考紫书P241
单调队列是一种特殊的队列,它能在队列两端进行删除操作,并始终维护队列保持一种单调性。
以求窗口最小值为例,我们需要维护一个内部元素值递减的队列来模拟滑动窗口,使每一次查询取队首元素即为答案。如何维护呢?有以下两种操作:

1.从队尾插入元素:当有新元素需要入队时,让它与当前队尾元素进行比较,若它小于等于当前队尾元素(即破坏了原队列的单调性),那么删除队尾元素,并继续比较队尾与新元素,直到找到一个队尾大于新元素时,将新元素插入到队尾。被删除的元素既比新元素大,又会比新元素先滑出窗口,因此肯定不会成为答案。这个操作不断维护了队列中的最值。

2.删除队首元素:由于序列中的元素当且仅当在滑动窗口时有效,因此,当队首元素不在滑动窗口内时,就删除队首元素。这个操作维护了数据的临时性。

元素是否过时与其下标有关,因此我队列中存储的是序列数对应的下标,这样每当要入队一个数,只需访问队首元素所存下标在序列中对应的数即可得到答案。
这样做尽管插入元素时有时需删除多个数,但每个数最多被删除一次,因此时间复杂度为O(n).

代码

数组模拟单调队列

#include <iostream>
#include <cstdio>
#include <cstring>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void get_min()  
{
    int head,tail,i;
    memset(Q,0,sizeof(Q));
    head=1;tail=1;
    Q[tail]=1;
    Min[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((head<=tail)&&(a[i]<a[Q[tail]]))//删除队尾元素
            tail--;                            //插入队尾元素
        Q[++tail]=i;
        while ((head<=tail)&&(Q[head]<i-k+1)) //删除队首过时元素
            head++;
        /*由于每一层循环只添加一个数,因此每次最多删除一次队首,
        故可改成if语句*/
        Min[i]=a[Q[head]];                         //访问答案
    }
}
void get_max() 
{
    int head,tail,i;
    memset(Q,sizeof(Q),0);
    head=1;tail=1;
    Q[tail]=1;
    Max[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((head<=tail)&&(a[i]>a[Q[tail]]))
            tail--;
        Q[++tail]=i;
        while ((head<=tail)&&(Q[head]<i-k+1))
            head++;
        Max[i]=a[Q[head]];
    }
}
int main()
{
    int i;
    scanf("%d%d",&n,&k);
    for (i=1;i<=n;i++)
        scanf("%d",&a[i]);
    get_min();
    get_max();

    for (i=k;i<n;i++)
        printf("%d ",Min[i]);
    printf("%d\n",Min[n]);
    for (i=k;i<n;i++)
        printf("%d ",Max[i]);
    printf("%d\n",Max[n]);
    return 0;
}

使用STL中的双端队列deque容器

#include <iostream>
#include <cstdio>
#include <cstring>
#include <deque>
#define MAXN 1000010
using namespace std;
int a[MAXN],Min[MAXN],Max[MAXN],Q[MAXN],n,k;
void Get_Min()
{
    deque<int> Q;
    int i;
    Q.push_back(1);
    Min[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((!Q.empty())&&(a[i]<a[Q.back()]))
            Q.pop_back();
        Q.push_back(i);
        while ((!Q.empty())&&(Q.front()<i-k+1))
            Q.pop_front();
        Min[i]=a[Q.front()];
    }
}
void Get_Max()
{
    deque<int> Q;
    int i;
    Q.push_back(1);
    Max[1]=a[1];
    for (i=2;i<=n;i++)
    {
        while ((!Q.empty())&&(a[i]>a[Q.back()]))
            Q.pop_back();
        Q.push_back(i);
        while ((!Q.empty())&&(Q.front()<i-k+1))
            Q.pop_front();
        Max[i]=a[Q.front()];
    }
}
int main()
{
    int i;
    scanf("%d%d",&n,&k);
    for (i=1;i<=n;i++)
        scanf("%d",&a[i]);
    Get_Min();
    Get_Max();

    for (i=k;i<n;i++)
        printf("%d ",Min[i]);
    printf("%d\n",Min[n]);
    for (i=k;i<n;i++)
        printf("%d ",Max[i]);
    printf("%d\n",Max[n]);
    return 0;
}
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值