单调队列

单调队列

一、问题引入

    有一个数列为:8, 7, 12, 5, 4, 2,  16,  9,  17,8,每次从左到右选取k个数,输出每个区间中的最大值。

    这可以看做是一个窗口从左边移到右边的过程。

    暴力方法自然是枚举一个右端点,然后通过for循环一个个比较,算出k个中的最大值。如果数据量较大,显然会超时。

    因此,需要一种更快速的求区间最值的办法,单调队列就是不错的选择。

二、算法分析

    原本的暴力做法,之所以会造成超时,是因为对每一个值,都进行了重复的比较。就是在找当前的f(i)的时候,i的前面k-1个数其实在算f(i-1)的时候我们就比较过了。那么我们能不能保存上一次的结果呢?当然主要是i的前k-1个数中的最大值了。答案是可以,这就要用到单调递减队列。

    当然,可以维护单调递减队列,自然也可以维护单调递增队列,道理是一样的,此处不做赘述。

三、算法实现

   单调递减队列是这么一个队列,它的头元素一直是队列当中的最大值,而且队列中的值是按照递减的顺序排列的。我们可以从队列的末尾插入一个元素,可以从队列的两端删除元素。

1. 插入元素:为了保证队列的递减性,我们在插入元素v的时候,在队尾的元素大于v之前,不断地将队尾元素出队,这个时候我们才将v插入到队尾。

    为什么要让队尾元素出队?

    我们所要求的是区间的最大值,那么既然当前值大于等于队尾元素,而且下标还更大,那它一定比队尾元素更有可能是后面可能的答案。因此,队尾元素失去了意义,完全可以出队。

    为什么要将目前的v保留呢?

    虽然v可能不是目前的最值,但是有可能是后面窗口中的最值,因此应保留。

2.删除元素:由于我们只需要保存i的前k-1个元素中的最大值,下标小于 i-k+1的元素不在窗口里,所以当index[队首元素]<i-k+1时,将队首元素删除。

四、维护举例(摘自曾妞妞的论文)

对于上面问题中的数列:8, 7, 12, 5, 4, 2,  16,  9,  17,8,可以构造一个长度为3的单调递减队列。

i=1:插入8,队列为:(8)      {i=1<3,不输出队首8}

i=2:插入7,队列为:(8,7)    {i=2<3,不输出队首8}

i=3:插入12,队列为:(12,8,7){12大于7和8,依次删除队尾的7和8,并插入12到队列,且i=3了,故输出队首的12}

i=4:插入5,队列为:(12,5)    {i≥3,输出队首12}

i=5:插入4,队列为:(12,5,4) {i≥3,输出队首12}

i=6:插入2,队列为:(12,5,4,2){i≥3,2插入后,因为队列中的元素多于了3个,队首的12没用了故被删除,输出此时的队首5,}

i=7:插入16,队列为:(16  5,4,2) {16大于队尾的2、4、5,依次删除2、4、5,并输出队首16}

i=8:插入9,队列为:(16,9)        {i≥3,输出队首16}

i=9:插入17,队列为:(17 16,9)    {i≥3,输出队首17}

i=10:插入8,队列为(17,8)      {i≥3,输出队首17}

五、代码实现

#include
    
    
     
     
#include
     
     
      
      
using namespacestd;
const intMAXN=1e6+5;
intn,k,maxa[MAXN],a[MAXN];
struct big
{
    int num,index;
}q[MAXN];
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=1;i<=n;i++)    cin>>a[i];
    int head,tail;
    head=1,tail=0;   
    for(int i=1;i<=n;i++)
    {
       while(head<=tail&&q[tail].num<=a[i])   tail--;
       tail++;
       q[tail].num=a[i];
       q[tail].index=i;
       while(head<=tail&&q[head].index<(i-k+1))  head++;          
       if(i>=k)   cout<
      
      
       
       <<" ";
    }
    return 0;
}
      
      
     
     
    
    

六、复杂度分析

    时间复杂度如何计算?入队,出队会不会耗掉很多时间?似乎不太确定。

    换一种思路,从每个数被操作了多少次来计算。不难发现,每个数最多入队一次,出队一次,因此时间复杂度为O(N)级别的。

    至于空间复杂度,队列长度至多为N,因此也为N。

    因此,单调队列不失为一种效率高,空间小的数据结构,是优化时空复杂度的不错选择。

七、应用范围

   单调队列可以应用于查询最值,可以维护具有单调性的序列,经常用于优化DP。它是一种常用、速度快。编程容易的数据结构。你,值得拥有。

八、习题应用

POJ 2823

SMOJ 诺诺的队列

SMOJ 松果

SMOJ 放假

SMOJ 伟大的航路

SMOJ 道路游戏

SMOJ 玫瑰华尔兹



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值