滑动窗口的最大值

本文详细介绍了如何使用单调队列解决滑动窗口最大值的问题,包括队头出队和队尾入队的策略,并给出了具体的代码实现。通过举例说明了算法的每一步操作,以及如何优化使得时间复杂度为O(n)。此外,还提到了使用STL中的deque作为单调队列的替代方案,并讨论了如何调整算法以找到窗口内的最小值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

滑动窗口的最大值在这里插入图片描述

题目描述


核心思路

这种滑动窗口的题都可以考虑用单调队列来求解。

单调队列:队列内的元素是单调的,递增或者递减

本题采用单调队列来存储当前窗口单调递减的元素,并且队头是窗口内的最大值,队尾是窗口内的尾元素,也就是说,队列从队头到队尾对应窗口内从最大值到尾元素的一个子序列。

  • 队头出队:当队头的元素从窗口中滑出时,队头元素出队head++。比如原序列24 【58 54 3】 15 13,队列中队头元素是58,然后窗口向右滑动一个,变成了24 58 【54 3 15】 13,那么此时原来的队头元素58也必须出队了,因为我们只考虑窗口内的元素,而不考虑窗口外的元素。因此如果队列中的队头元素在原序列中如果已经脱离了窗口,则要出头。

在这里插入图片描述

  • 队尾入队:当新元素滑入窗口时,要把新元素从队尾插入,分为两种情况:

    • 直接再队尾元素后面插入新元素:如果新元素小于队尾元素,那就直接把这个新元素从队尾插入,tail++,这样可以保证队列单调递减,因为这个新元素可能在当队列中它前面的最大值出队后成为最大值。如下图所示,原来队列中队尾元素是6,现在来了一个新元素4,由于新元素4小于队尾元素6,因此可以加入队列。

    在这里插入图片描述

    • 先删除队尾元素再加入新元素:如果新元素大于等于队尾元素,那就删除队尾元素(因为此时队尾元素不可能成为队列中的最大值了),删除方法是tail–,即从队尾出队。循环删除,直到队空或者遇到队列中的某个队尾元素大于新元素时,那么就转变成了第一种,将这个新元素插入到这个队尾元素后面即可。

      如下图所示,原来队列中有6、4,来了新元素9,由于新元素9大于队尾元素4,所以4出队,继续,由于新元素9大于队尾元素6,所以6出队,此时队列为空,那么就直接将这个新元素9放入到队列中即可。

通过上面这么做,每次都能从队头取得窗口中的最大值。

分析步骤如下:

  • 当i=1时,窗口内只有一个元素2,因此队列中也只有一个元素2,如下图

在这里插入图片描述

  • 当i=2时,窗口内有两个元素2、6,此时队列中的元素是6,如下图

在这里插入图片描述

  • 当i=3时,窗口内有三个元素2、6、4,此时队列中的元素是6、4,如下图

    在这里插入图片描述

  • 当i=4时,窗口右移一个,那么新的窗口内有三个元素6、4、9,此时队列中的元素是9,如下图

在这里插入图片描述

  • 当i=5时,窗口右移一个,那么新的窗口内有三个元素4、9、8,此时队列中的元素是9、8,如下图

在这里插入图片描述

  • 当i=6时,窗口右移一个,那么新的窗口内有三个元素9、8、5,此时队列中的元素是9、8、5,如下图

在这里插入图片描述

  • 当i=7时,窗口右移一个,原来队列中的队头元素9在原序列中已经滑出了窗口,因此此时队列中的9也必须出队了,那么新的窗口内有三个元素8、5、5,此时队列中的元素是8、5,如下图

在这里插入图片描述

  • 当i=8时,窗口右移一个,原来队列中的队头元素8在原序列中已经滑出了窗口,因此此时队列中的8也必须出队了,那么新的窗口内有三个元素5、5、2,此时队列中的元素是5、2,如下图

在这里插入图片描述

每次操作完成后,队头元素都是窗口内的最大值。
经过模拟可以发现,指向完q[++tt]=i后,那么此时q[tt]的值就是 i i i的值,而 i i i它指向就是当前窗口的右端点,那么也就是说,q[tt]也是指向当前窗口的右端点。

上面我们模拟时,队列中存储的是元素,但是如果我们让队列存储的是元素,由于可能有重复元素,那么是很难判断队头元素该什么时候才能出队。但是如果我们让队列存储的是元素的下标,比如此时队头元素的下标为 x x x,窗口最后一个元素的下标是 i i i,设窗口大小为m,那么当 i − x > m i-x>m ix>m时,则说明我们的窗口右移了一个,那么原来队列中的队头元素就不在窗口中了,因此这样就可以说明队头出队了。
在这里插入图片描述

由于每个元素最多入队和出队各一次,应该该算法的时间复杂度是 O ( n ) O(n) O(n)

求窗口内的最大值代码如下:

int hh=0,tt=-1;
for(int i=1;i<=n;i++)//遍历序列
{
    //当队列不空,并且如果q[hh]不在窗口[i-m+1,i]中,则队头出队
    if(hh<=tt&&q[hh]<i-m+1)
        hh++;
    //当队头元素不为空,并且新来的元素a[i]大于等于队尾元素时,队尾元素出队
    while(hh<=tt&&a[i]>=a[q[tt]])
        tt--;
    //到这里说明这个新元素可以直接加入队列中,下标入队
    q[++tt]=i;
    //当达到窗口的大小m时才能输出,比如窗口大小m=3,那么刚开始窗口为0,1,2时,都不满足窗口的大小,不能输出
    //这里如果i是从1开始取,那么就要写成i>m-1;如果i是从0开始取,那么就要写成i>=m-1
    if(i>m-1)//其实这里写成i>=m也是可以的
        printf("%d ",a[q[hh]]);
}

STL中的双端队列deque也可以代替单调队列:

deque<int>q;
for(int i=1;i<=n;i++)
{
    if(!q.empty()&&q.front()<i-m+1)
        q.pop_front();
    while(!q.empty()&&a[i]>=a[q.back()])
        q.pop_back();
    q.push_back(i);
    if(i>m-1)
        printf("%d ",a[q.front()]);
}

对于如果要求窗口内的最小值,那么其实我们可以让这个单调队列是递增的,那么此时队头元素就是最小值。代码如下:

int hh=0,tt=-1;
for(int i=1;i<=n;i++)
{
    if(hh<=tt&&q[hh]<i-m+1)
        hh++;
    while(hh<=tt&&a[i]<=a[q[tt]])
        tt--;
    if(i>m-1)
        printf("%d ",a[q[hh]]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计小酱蟹不肉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值