AcWing 154. 滑动窗口(单调队列)

题目描述


本题强推b站 董晓算法 老师的讲解,通俗易懂,我也是借鉴了里面许多关键思想。9.74 单调队列 滑动窗口最大值——信息学竞赛培训课程


分析:

滑动窗口可以先考虑暴力解,对长度为 m m m 的窗口。构建一个模型,利用双指针, i i i 指针为外层循环指针, j j j 为内层循环指针,对窗口内的元素的下标进行循环。如下:

在这里插入图片描述
计算窗口最小值需要 O ( m ) O(m) O(m),移动 n − m + 1 n-m+1 nm+1 次,总时间复杂度为 O ( n m ) O(nm) O(nm)
代码为:

for (int i = m; i <= n; i ++)
{
	int mi = a[i];
	for (int j = i - m + 1; j <= i; j ++)
	{
		mi = min(mi, a[j]);
	}
	printf("%d ", mi);
}

接下来我们使用单调队列来对问题进行优化,那么我们先看什么是单调队列,如何构造单调队列,以及为什么要用单调队列。

我们先考虑滑动窗口中的最大值问题。

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

  1. 队头出队:当队头的元素从窗口滑出时,队头元素出队(head ++);
  2. 队尾入队:当新元素滑入窗口时,要把新元素从队尾插入,分两种情况:
    ① 直接插入:如果新元素小于队尾元素,那就直接从队尾插入(++ tail),因为它可能在前面的最大值滑出窗口后成为最大值。
    ② 先删后插:如果新元素大于等于队尾元素,那就先删除队尾元素(因为它不可能称为窗口中的最大值,新来的就比它大),删除方法是 tail --,即从队尾出队。循环删除,直到队空或遇到一个大于新元素的值,插入其后(++ tail)。

这样做,每次都能从队头取得窗口中的最大值。如何判断队头元素何时出队呢?当我们在队列中存储的是元素值时,这是很难判断的,但若存储的是该元素所对应的下标值就容易判断了:可根据 i i i 和队头元素的下标的差与窗口大小进行比较。

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


代码(C++)

#include <iostream>

using namespace std;

/*
求最大值时,用单调队列存储当前窗口内单调递减的元素,队头是窗口内的最大值,队尾是窗口内的尾元素。
求最小值时,用单调队列存储当前窗口内单调递减的元素,队头是窗口内的最小值,队尾是窗口内的尾元素。
*/
const int N = 1000010;
int a[N], que[N];

int main()
{
    int n, k;
    scanf("%d%d", &n, &k);

    for (int i = 0 ; i < n; i ++) scanf("%d", &a[i]);
    int head = 0, tail = -1;

    for (int i = 0; i < n; i ++)
    {
        // 下标为 que[head] 的元素是否还在当前窗口的最左端,若不在,则单调队中队头为上个窗口中最小值的下标
        // 进行队头出队,head 自动指向第一个比 a[que[head]] 小的元素的下标,且在当前窗口内
        if (head <= tail && i - k + 1 > que[head]) head ++;

        // 若当前值小于等于队尾元素时,则队尾元素不可能称为窗口最小值
        // 则将队尾元素出队
        while (head <= tail && a[que[tail]] >= a[i]) tail --;

        // 下标入队,便于队头出队,方便处理下一个滑动窗口
        que[++ tail] = i;

        // 使用队头中的最小值
        if (i >= k - 1) printf("%d ", a[que[head]]);
    }
    puts("");

    // 求窗口最大值情况相似
    head = 0, tail = -1;
    for (int i = 0; i < n; i ++)
    {
        if (head <= tail && i - k + 1 > que[head]) head ++;
        while (head <= tail && a[que[tail]] <= a[i]) tail --;
        que[++ tail] = i;

        if (i >= k - 1) printf("%d ", a[que[head]]);
    }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值