单调栈与单调队列
单调栈
单调栈,顾名思义就是一种具备单调性质(内部元素满足单调性)的栈结构
现在来看一道例题:
向右看齐
有 \(n\) 个人从左到右站成一排,每个人都有一个身高,现要求所有人向右看齐,问每个人可以看到的最远的人是哪一个?(一个人右边第一个身高大于等于他的人会挡住这个人的视线 )。没有则输出 \(-1\)
数据范围:\(1\le n\le 10^6\)
简化题意:
给定含有 \(n\) 个数的连续序列 \(h\),对于每一个 \(i\) 找到一个最小的 \(j\in [i+1,n]\),使得 \(h[i]<h[j]\),若不存在则输出 \(-1\) 。
分析:
先考虑暴力,对于每一个位置 \(i\) 都向右寻找一个 \(j\),此时极限复杂度大约为 \(O(n^2)\),无法通过本题
为什么会超时呢?
假设序列 \(h = \{5,4,3,2,1\}\),从右到左计算,步骤如下:
- 在 \(h_5\) 时直接得出答案 \(-1\)
- 在 \(h_4\) 时查询 \(h_5\),得出答案 \(-1\)
- 在 \(h_3\) 时查询 \(h_4,h_5\),得出答案 \(-1\)
- 在 \(h_2\) 时查询 \(h_3,h_4,h_5\),得出答案 \(-1\)
- 在 \(h_1\) 时查询 \(h_2,h_3,h_4,h_5\),得出答案 \(-1\)
可以发现每一个数都被查询了很多遍,这就是超时的原因
这时需要用一个栈来维护可能存在的解,维护步骤如下:
在从右到左扫描的过程中,若当前的值大于栈顶,则栈顶一定不优(永远也不会用到,因为有更优的解),出栈并放入新的解。
可以发现,这样维护的栈满足单调性,且栈顶元素是最大的。
时间复杂度见底部
代码:
代码使用STL栈,计算方式是从右至左
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6;
int n;
int a[maxn + 5];
stack<int> st;
int ans[maxn + 5];
int main() {scanf("%d",&n);for (int i = 1;i <= n;i ++) scanf("%d",&a[i]);for (int i = n;i >= 1;i --) {while (!st.empty() && a[st.top()] < a[i]) st.pop();ans[i] = st.empty() ? -1 : st.top();st.push(i);}for (int i = 1;i <= n;i ++) printf("%d\n",ans[i]);return 0;
}
单调队列
单调队列,本质上就是在单调栈的底部开一个口,用于排出非法解
经典例题:
滑动窗口
给定一个长度为 \(n\) 的数字序列 \(a\) 和一个正整数 \(k\),对于每一个 \(i\in[1,n-k+1]\) ,求区间 \([i,i+k-1]\) 的最大值与最小值
数据范围:\(1\le n \le 10^7\)
分析:
如果数据再小一点(\(n\le10^6\))就可以用ST表用通法解决。但显然,这道题并不是普通的区间最值查询,它固定了查询区间长度。也就是说在区间向右滑动时,一个元素有且可能被加入(删除)区间 \(1\) 次,这满足上面对单调栈”永远不会用到的元素“的分析。
与单调栈思路相同,只是增加了一个条件,若栈底(队头)元素的坐标小于给定区间的开头,出队。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7;
int n,k;
int a[maxn + 5];
deque<int> qmax,qmin;
int mx[maxn + 5],mn[maxn + 5];
int main() {scanf("%d %d",&n,&k);for (int i = 1;i <= n;i ++) scanf("%d",&a[i]);for (int i = n;i >= 1;i --) {while (!qmax.empty() && a[qmax.back()] < a[i]) qmax.pop_back(); // 删除不优解while (!qmax.empty() && qmax.front() > i + k - 1) qmax.pop_front(); // 排除在边界外的答案qmax.push_back(i);if (i <= n - k + 1) mx[i] = a[qmax.front()];while (!qmin.empty() && a[qmin.back()] > a[i]) qmin.pop_back();while (!qmin.empty() && qmin.front() > i + k - 1) qmin.pop_front();qmin.push_back(i);if (i <= n - k + 1) mn[i] = a[qmin.front()];}for (int i = 1;i <= n - k + 1;i ++) printf("%d ",mn[i]);printf("\n");for (int i = 1;i <= n - k + 1;i ++) printf("%d ",mx[i]);printf("\n");return 0;
}
时间复杂度
每个元素仅入栈出栈(出队入队) \(1\) 次,时间复杂度为\(O(n)\) 。