单调栈与单调队列
单调栈
单调栈是满足单调性的栈结构。
举例说明:
当向栈中插入元素时,为了维护栈的单调性(单调递增),需要弹出一些元素,而弹出元素的数目应该是最小的,之后再将元素插入到栈顶。
例如,栈中目前有元素
cpp
// 插入元素 x
stack<int> stk;
while (stk.size() && stk.top() <= x) {
stk.pop();
}
stk.push(x);洛谷 P5788. 【模板】单调栈
题意:给定长度为
从右往左扫描,用栈维护"右边可能成为答案的下标"。对当前位置
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 3000010;
int a[N], ans[N], stk[N];
int n, top;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = n; i >= 1; i--) {
while (top && a[stk[top]] <= a[i]) top--;
ans[i] = top ? stk[top] : 0;
stk[++top] = i;
}
for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
return 0;
}单调队列
问题:
给定长度为
的整数数组,求每连续 个元素中的最大值和最小值?
暴力想法自然是两重循环,枚举每 TLE。
此时可以使用单调队列。
单调队列指的是队列中的元素满足单调递增或单调递减的性质。
例如我们维护一个单调递增队列
有如下序列:
操作如下:
| 操作 | 结果 |
|---|---|
| 此时 | |
总结,单调队列每一次操作遵循以下流程:
- 第一步从队头开始清理已不在窗口的元素
- 第二步从队尾开始清理不满足单调性质的元素
- 第三步将元素入队
洛谷 P1886. 滑动窗口 /【模板】单调队列
题意:给定长度为
注意:求最小值维护单调递增(不减)队列,求最大值维护单调递减(不增)队列,两者完全对称。每次插入
- 弹出队头越界的下标(
); - 弹出队尾不满足单调性的元素;
入队,当 时队头即为当前窗口的最值。
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int a[N], q[N];
int n, k;
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
// 最小值:维护单调递增队列
int hh = 0, tt = -1;
for (int i = 1; i <= n; i++) {
if (hh <= tt && q[hh] < i - k + 1) hh++; // 1. 清理窗口外的队头
while (hh <= tt && a[q[tt]] >= a[i]) tt--; // 2. 清理破坏单调性的队尾
q[++tt] = i; // 3. 入队
if (i >= k) cout << a[q[hh]] << ' ';
}
cout << endl;
// 最大值:维护单调递减队列
hh = 0, tt = -1;
for (int i = 1; i <= n; i++) {
if (hh <= tt && q[hh] < i - k + 1) hh++;
while (hh <= tt && a[q[tt]] <= a[i]) tt--;
q[++tt] = i;
if (i >= k) cout << a[q[hh]] << ' ';
}
cout << endl;
return 0;
}队列中每个元素至多入队、出队一次,总复杂度
应用:单调队列除了解决滑动窗口最值外,还常用于优化形如
