题目描述

给一个长度为N的数组,一个长为K的滑动窗体从最左端移至最右端,你只能看到窗口中的K个数,每次窗体向右移动一位,你的任务是找出窗体在各个位置时的最大值和最小值。

解题思路

单调队列的经典题目,正如邓老师说的那样,每次区间滑动一个单位,区间内元素从图片说明
变为了图片说明 可以看出,变化的部分,只有a[l]和a[r+1],完全没必要把中间部分去遍历很多遍。
这时候就请来了强大的单调队列大大,开辟一个队列,让里面保存的都是可能是答案的下标,并且当前位置的答案下标保存在对头中。
具体的实现过程注释的比较详细,当前元素如果要入队,之前的对头下标如果和自己差距大于m,说明以前的下标不合法了,队头出队。
拿求最小值来说,如果队尾元素大于等于当前a[i],a[i]入队后,最小值不可能是当前队尾了,队尾元素出队。反复循环直到队尾元素小于a[i]。

Code

#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

const int N = 1e6 + 7;
int a[N], que[N];

int main() {
    int n = read(), m = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read();
    int l = 0, r = 0; //单调队列队头和队尾
    que[r++] = 1;
    if (m == 1)    printf("%d ", a[1]); //后面从2开始特判下m
    for (int i = 2; i <= n; ++i) {
        //队中存在元素,并且之前的最小元素下标已经不在当前m范围内 出队
        if (r > l && i - que[l] >= m)    ++l;
        //  当前队尾值大于等于a[i] a[i]入队后,最小值一定不会是队尾了,队尾出队
        while (r > l && a[que[r - 1]] >= a[i])    --r;

        que[r++] = i;
        if (i >= m)
            printf("%d ", a[que[l]]); //队头中保存的就是最小值下标位置
    }
    puts("");
    // 最大值类似
    l = r = 0;
    que[r++] = 1;
    if (m == 1)    printf("%d ", a[1]);
    for (int i = 2; i <= n; ++i) {
        //队中存在元素,并且之前的最大元素下标已经不在当前m范围内 出队
        if (r > l && i - que[l] >= m)    ++l;
        //  当前队尾值小于等于a[i] a[i]入队后,最大值一定不会是队尾了,队尾出队
        while (r > l && a[que[r - 1]] <= a[i])    --r;

        que[r++] = i;
        if (i >= m)
            printf("%d ", a[que[l]]); //队头中保存的就是最大值下标位置
    }
    puts("");
    return  0;
}