单调栈与单调队列
单调栈
单调栈是一种内部元素具有单调性的栈,可以解决求“以某个值为最值的最大区间”等问题。
原理
以下摘自 洛谷B3666 求数列所有后缀最大值的位置 题目描述:
给定一个数列
每次操作结束后,请你找到当前
考虑用一个栈维护当前数列所有后缀最大值的位置(即下标),初始时栈为空。
易证栈中元素对应的数是单调递减的,因此称为单调栈。
考虑
-
若栈顶元素
满足 ,则此时 不再是后缀最大值,因此弹出 。 -
若栈顶元素
满足 ,则此时 仍然是后缀最大值,不操作。 -
由后缀最大值的定义知
一定是后缀最大值, 入栈。
由于单调栈中的元素对应的数单调递减,所以应删去的元素仅存在于栈顶,因此只需要重复执行第一步直到
类似地,单调栈可以维护数列所有后缀最值或前缀最值,这是单调栈维护的本质信息。
实现
使用 STL 的 std::vector
容器而非 std::stack
实现单调栈,原因是 std::stack
时空常数巨大且 std::vector
支持 std::stack
的全部功能。
以下为核心代码:
std::vector<int> s; for (int i = 1; i <= n; i++) { while (!s.empty() && a[s.back()] <= a[i]) s.pop_back(); s.push_back(i); } for (auto i : s) std::cout << i << " "; std::cout << "\n";
算法分析
每个元素只会在被插入时入栈一次,也只会出栈至多一次,所以总时间复杂度是
基础应用
单调栈可以解决求“以某个值为最值的最大区间”的问题。
以下为 P5788 【模板】单调栈 - 洛谷 题目描述:
给出项数为
定义函数
试求出
每一个被弹出的栈顶元素
#include <iostream> #include <vector> int a[3000005], ans[3000005]; std::vector<int> s; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int n; std::cin >> n; for (int i = 1; i <= n; i++) std::cin >> a[i]; for (int i = 1; i <= n; i++) { while (!s.empty() && a[s.back()] < a[i]) { ans[s.back()] = i; s.pop_back(); } s.push_back(i); } for (int i = 1; i <= n; i++) std::cout << ans[i] << " "; std::cout << "\n"; return 0; }
类似地,对于一个数列
单调队列
单调队列是一种内部元素具有单调性的队列,可以解决求“区间内最值”等问题。
原理
以下摘自 洛谷 B3667 求区间所有后缀最大值的位置 题目描述:
给定一个长度为
后缀最大值的定义同上。
仍考虑单调栈的思想。设当前处理的子区间为
因此当询问到
栈无法删除底部元素,因此使用双端队列实现,因此这种数据结构被称为单调队列。
类似地,单调队列可以维护数列中所有长度为
实现
使用 STL 的 std::deque
容器实现单调队列。但 std::deque
时空常数巨大,必要时应改为手写队列。
以下为核心代码:
std::deque<int> s; for (int i = 1; i <= n; i++) { int l = std::max(i - k + 1, 1); while (!s.empty() && s.front() < l) s.pop_front(); while (!s.empty() && a[s.back()] < a[i]) s.pop_back(); s.push_back(i); if (i >= k) { for (auto i : s) std::cout << i << " "; std::cout << "\n"; } }
算法分析
与单调栈类似,每个元素只会在被插入时入队一次,也只会出队至多一次,所以总时间复杂度是
基础应用
单调队列可以解决求“给定长度的区间内最值”的问题。
以下为 P1886 滑动窗口 /【模板】单调队列 - 洛谷 题目简述:
给出项数为
以最大值为例,单调队列中存储的是每个区间中所有后缀最大值的位置,易证单调队列中元素对应的数单调递减,因此队首对应的即为这个区间的最大值。
#include <iostream> #include <deque> #include <algorithm> int a[1000005]; std::deque<int> s; int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout.tie(nullptr); int n, k; std::cin >> n >> k; for (int i = 1; i <= n; i++) std::cin >> a[i]; for (int i = 1; i <= n; i++) { int l = std::max(i - k + 1, 1); while (!s.empty() && s.front() < l) s.pop_front(); while (!s.empty() && a[s.back()] >= a[i]) s.pop_back(); s.push_back(i); if (i >= k) std::cout << a[s.front()] << " "; } std::cout << "\n"; s.clear(); for (int i = 1; i <= n; i++) { int l = std::max(i - k + 1, 1); while (!s.empty() && s.front() < l) s.pop_front(); while (!s.empty() && a[s.back()] <= a[i]) s.pop_back(); s.push_back(i); if (i >= k) std::cout << a[s.front()] << " "; } std::cout << "\n"; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步