POJ 2823:給定長度 n 數組,窗口大小 k,求每個窗口的最小值和最大值。本文介紹單調隊列 O(n) 解法:維護兩個雙端隊列,分別單調遞增(最小值)和單調遞減(最大值)。
i,從隊尾刪除所有 a[tail] >= a[i] 的下標,再推入 i。≤ i - k(滑出窗口),彈出隊首。i ≥ k-1 時)。#include <cstdio>
#include <deque>
using namespace std;
const int MAXN = 1000010;
int a[MAXN];
int minAns[MAXN], maxAns[MAXN];
int main() {
int n, k;
while (scanf("%d%d", &n, &k) == 2) {
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
deque minQ, maxQ;
int minCnt = 0, maxCnt = 0;
for (int i = 0; i < n; ++i) {
// 最小值隊列:單調遞增
while (!minQ.empty() && a[minQ.back()] >= a[i]) minQ.pop_back();
minQ.push_back(i);
if (minQ.front() <= i - k) minQ.pop_front();
if (i >= k - 1) minAns[minCnt++] = a[minQ.front()];
// 最大值隊列:單調遞減
while (!maxQ.empty() && a[maxQ.back()] <= a[i]) maxQ.pop_back();
maxQ.push_back(i);
if (maxQ.front() <= i - k) maxQ.pop_front();
if (i >= k - 1) maxAns[maxCnt++] = a[maxQ.front()];
}
for (int i = 0; i < minCnt; ++i) {
if (i) printf(" ");
printf("%d", minAns[i]);
}
printf("\n");
for (int i = 0; i < maxCnt; ++i) {
if (i) printf(" ");
printf("%d", maxAns[i]);
}
printf("\n");
}
return 0;
}
int minQ[MAXN], maxQ[MAXN];
int minHead=0, minTail=0, maxHead=0, maxTail=0;
for (int i = 0; i < n; ++i) {
while (minHead < minTail && a[minQ[minTail-1]] >= a[i]) --minTail;
minQ[minTail++] = i;
if (minQ[minHead] <= i - k) ++minHead;
if (i >= k-1) minAns[minCnt++] = a[minQ[minHead]];
while (maxHead < maxTail && a[maxQ[maxTail-1]] <= a[i]) --maxTail;
maxQ[maxTail++] = i;
if (maxQ[maxHead] <= i - k) ++maxHead;
if (i >= k-1) maxAns[maxCnt++] = a[maxQ[maxHead]];
}
i - k 為滑出窗口的最大下標(0-based)。單調隊列是「滑動窗口最值」的標準解法,將暴力 O(nk) 優化至 O(n)。核心不變量:隊列內下標對應值單調,隊首為窗口最值。掌握「入隊維護單調、出隊檢查過期、隊首取值」三步驟,可解決所有滑動窗口最值/最小/最大/中位數(配合雙堆)問題。