滑动窗口最值

问题:给出一个整数序列a[n],现在有一个宽度为k的窗口从最左端往右移动,求各个位置处窗口内部元素的最小值和最大值。

最简单的方法是O(nk)暴力,会超时。

可以看成普通的区间最值问题,用线段树和ST表来做,时间复杂度为O(nlogn),比前一种方法要快,但仍会超时,下面是用线段树来做。

#include <iostream>
#include <cstdio>
#include <climits>
using namespace std;
const int N = 1000005;
int n, k, a[N], mx[4*N], mn[4*N];
void pushup(int x) {
    mx[x] = max(mx[2*x], mx[2*x+1]);
    mn[x] = min(mn[2*x], mn[2*x+1]);
}
void build(int x, int l, int r) {
    if (l == r) {
        mx[x] = mn[x] = a[l];
        return;
    }
    int mid = (l + r) / 2;
    build(2*x, l, mid);
    build(2*x+1, mid+1, r);
    pushup(x);
}
int qmn(int x, int l, int r, int L, int R) {
    if (R < l || r < L) return INT_MAX;
    if (L <= l && r <= R) return mn[x];
    int mid = (l + r) / 2;
    return min(qmn(2*x, l, mid, L, R), qmn(2*x+1, mid+1, r, L, R));
}
int qmx(int x, int l, int r, int L, int R) {
    if (R < l || r < L) return INT_MIN;
    if (L <= l && r <= R) return mx[x];
    int mid = (l + r) / 2;
    return max(qmx(2*x, l, mid, L, R), qmx(2*x+1, mid+1, r, L, R));
}
int main() {
    scanf("%d%d", &n, &k);
    k -= 1;
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    build(1, 1, n);
    for (int i = 1; i + k <= n; i++)
        printf("%d%s", qmn(1, 1, n, i, i+k), i+k == n ? "\n" : " ");
    for (int i = 1; i + k <= n; i++)
        printf("%d%s", qmx(1, 1, n, i, i+k), i+k == n ? "\n" : " ");
    return 0;
}

另外一种做法是用map来维护窗口中的元素,取最大和最小都是O(logk),总时间复杂度为O(nlogk),编码简单些,但也会超时。

#include <map>
#include <vector>
#include <cstdio>
using namespace std;
int a[1000005];
int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    map<int,int> mp;
    for (int i = 1; i < k; i++)
        mp[a[i]] += 1;
    vector<int> mx, mn;
    for (int i = k; i <= n; i++) {
        mp[a[i]] += 1;
        mn.push_back(mp.begin()->first);
        mx.push_back(mp.rbegin()->first);
        int key = a[i-k+1];
        if (--mp[key] == 0)
            mp.erase(key);
    }
    for (int i = 0; i < mn.size(); i++)
        printf("%d%s", mn[i], i + 1 == mn.size() ? "\n" : " ");
    for (int i = 0; i < mx.size(); i++)
        printf("%d%s", mx[i], i + 1 == mx.size() ? "\n" : " ");
    return 0;
}

最后是用单调队列来做,时间复杂度为O(n),由于POJ没开O2优化,用STL可能会超时,可以用数组来模拟队列,并加上快速IO。

#include <cstdio>
#include <deque>
#include <vector>
using namespace std;
int n, k, a[1000005];
deque<int> qmx, qmn;
vector<int> amx, amn;
void add(int x) {
    while (!qmx.empty() && qmx.back() < x)
        qmx.pop_back();
    qmx.push_back(x);
    while (!qmn.empty() && qmn.back() > x)
        qmn.pop_back();
    qmn.push_back(x);
}
void del(int x) {
    if (qmx.front() == x)
        qmx.pop_front();
    if (qmn.front() == x)
        qmn.pop_front();
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i < k; i++)
        add(a[i]);
    for (int i = k; i <= n; i++) {
        add(a[i]);
        amx.push_back(qmx.front());
        amn.push_back(qmn.front());
        del(a[i-k+1]);
    }
    for (int i = 0; i < amn.size(); i++)
        printf("%d%s", amn[i], i + 1 == amn.size() ? "\n" : " ");
    for (int i = 0; i < amx.size(); i++)
        printf("%d%s", amx[i], i + 1 == amx.size() ? "\n" : " ");
    return 0;
}
Table of Contents