单调队列应用--滑动窗口

 单调队列的经典例题,详情见154. 滑动窗口 - AcWing题库

给定长度为n(n<10^6)的数组,给定窗口长度k。

我们这里以输出滑动窗口内的最大值为例,最小值只需要对称的改一下符号即可。

单调队列的分析

我们对于单调队列,单调栈,kmp此类问题,都是从暴力算法中优化而来,对于此题,我们若是用暴力算法的话,即一般队列,便是用两层for循环,第一层从头开始遍历原数组,第二层每一次窗口移动时就暴力搜索一次此时窗口内的最大值输出,接着队头出列,队尾继续入列,分析一下,第一层循环n次,第二层固定的k次,那么时间复杂度就是O(n*m),如果二者大于10e5,显然是TLE,所以我们便有与kmp算法和单调栈算法类似的想法

能否删除掉一些答案不需要的元素?

答案是能的,如果我们的队列时刻有着单调的状态,那么队头时刻为最大值,我们就把需要O(k)的时间复杂度降低为了O(1),整体时间复杂度便是O(n)。

我们假设原来的序列是单调的,即a_{0}> a_{1}> a_{2}> a_{3}> a_{4},当新的元素a_{5}插入进来的时候,就分别有两(细致来说是三种)情况:

  1. a_{5}< a_{4}那么我们直接入队即可继续保持单调性
  2. a_{5}> a_{4}那么我们不妨思考一下,后入的元素总是比前面的元素后出队,如果说a_{5}一直在该队列中,那么永远也轮不到a_{4}作为答案(最大值)输出,此时队列中比a_{5}小的元素我们都应该删去  注意:这里就能看出来是双端删除的队列。
  3. a_{5}= a_{4}  当二者值相等的时候,我们仍然要删去前者,这是因为后入的元素在队列中存在的时间大于前者,我们称其更具有“优势” ,试分析如果单调队列中保留了相等的元素会出现什么情况?

 如此以来,我们便能够时时刻保证我们窗口是一直具有单调性的,每次只需要输出队头第一个元素即可。

代码的实现

在理解了单调栈之后再来看单调队列是很容易看懂的,但是代码实现却相对困难。

代码的实现,我们主要有以下的困难

最主要的就是如何去判断窗口内元素已满,需要删去队头元素呢?

如果我们是用一系列计算数组长度的函数的话,那么会带来额外的时间损耗,可能会TLE,所以我们开的队列里面存储的数据应该是原数组元素的下标,每一次新元素入队前,此时的队长简单等于队首元素和循环变量i做运算,但是我们不禁思考,如果中间有被删除的元素,那么用这样的计算方式会不会使长度错误的算长?

for(int i = 0 ; i < n ; i ++)
{
    if(not empty && q[hh] < i - k + 1) hh ++;

    while(not empty && a[i] >= a[q[tt]] ) tt--;

    q[++ tt] = i;

    if(i > m - 1) printf("%d", a[q[hh]]);
}

经过手动模拟,我们发现q[hh] < i - k + 1 是没有问题的判断 q[hh]记录的是当前队头的值,也就是当前窗口中最大值的下标,可以看到每次都是先去判断是否窗口内元素满了,再来删元素的,而i是指向当前的待入队的元素的下标,所以不可能会出现错误算长的情况,模拟一下列子就懂了

你可能感兴趣的:(动态规划,算法)