单调栈

基本概念

单调栈的基础结构是“栈”,即元素是LIFO的,只能从栈顶控制数据出入;单调表示从栈的一端到另一端遍历,元素是单调递增或者单调递减的。比如单调递减栈,从栈底到栈顶的元素可以是:

bottom 10 7 5 3 2 1 top

单调栈获取和处理元素都是在线的方式。

假设有一个有序序列,元素是

1 6 3 4 2 3 4 5 8 9 7

我们想用一个单调递增的栈来遍历这个序列,则栈内元素变化是:

1
1 6
1 3
1 3 4
1 2
1 2 3
1 2 3 4
1 2 3 4 5
1 2 3 4 5 8
1 2 3 4 5 8 9
1 2 3 4 5 7

单纯看上面的栈,可能没有意义。但是,设想一个这样的场景,我们需要在线获取满足下面条件的区间[l, r)
对于任意的m, l <= m <= r,都有arr[m] >= arr[l]
我们想要获取所有满足这种条件区间,反观上面的单调递减栈的进栈顺序,确实可以 满足这种条件,以前两个位例子:

1       # 一个元素必然是
1, 6   # 1, 6 也满足这个区间
1, 3   # 1, 6, 3 这个序列也是,只不过6没有在栈中

为了记录区间范围,我们可以把栈存储元素,改成存储下标,这样可以动态的获取满足条件的区间。

应用

首先明确,单调栈适合处理依赖序列单调性质的问题,同时注意,单调栈在某种意义上是在线算法,因为是动态增删元素。

最经典的应用是求解直方图最大矩形的面积,题目分析在最后的参考文章中有,这里不再赘述。明确一点,从当前柱子向右扩展,那么扩展的最大边界是第一个比该柱子低的柱子。如果柱子是递增的,则每一个都可以作为新的启点。

#include <iostream>
#include <stack>
#include <vector>
#include <algorithm>// 直方图最大矩形面积
int maxMartixArea(const std::vector<int>& line) {if (line.empty()) {return 0;}std::stack<int> st;int maxArea = -1;int N = line.size();for (int i = 0; i < line.size(); ++i) {while (!st.empty() && line[st.top()] >= line[i]) {int t = st.top();int s = (i - t) * line[t];maxArea = std::max(s, maxArea);  // 在线计算最大面积,处理连续递增的面积st.pop();}st.push(i);  // 每个元素入栈一次}// 处理栈中多余的元素if (!st.empty()) {  // 占位符N = st.top() + 1;}while (!st.empty()) {int t = st.top();int s = (N - t) * line[t];maxArea = std::max(s, maxArea);}return maxArea;
}int main() {return 0;
}

单调队列

基本概念

基于单调栈的一个扩展,当单调栈的栈底元素可以弹出的时候,单调栈即可转化单调队列。单调队列是一个受限的双端队列,因为队头只能弹出元素。由单调栈可以知道,单调栈维护的是单调区间。以上面的单调递增队列为例子,栈底的元素,永远是当前区间中最小的值,因此单调队列可以在O(1)O(1)O(1)的时间内,获取队首队尾区间的最值,因此单调的区间的最大用处,也在于获取区间的最值上。

一般来说,我们的区间需要满足某个递减条件,然后确定区间的长度,并求解区间的最值,队首元素会不断更替,以满足区间长度的需求。

应用

单调队列的最典型应用是求解滑动窗口最值。直接给出代码

std::vector<int> slideWinMaxNum(const std::vector<int>& arr, int winSize) {std::vector<int> res;if (arr.empty() || winSize <= 0 || arr.size() < winSize) {return res;}std::deque<int> deq;int N = arr.size();for (int i = 0; i < N; ++i) {while (!deq.empty() && arr[deq.back()] <= arr[i]) {deq.pop_back();}deq.push_back(i);if (i >= winSize - 1) {res.push_back(arr[deq.front()]);}if (i - deq.front() >= winSize) {deq.pop_front();}}
}

最大值减去最小值大于等于num的区间个数:

#include <iostream>
#include <vector>
#include <deque>/**  基本思路是,利用两个单调队列,在以某个元素作为区间左边界的情况下,*  不断更替区间范围,并动态获取最值,之后利用最值之差和下标之差,计算*  符合条件的子数组的个数*/int MaxSubMinNum(const std::vector<int>& arr, int num) {if (arr.empty()) {return 0;}std::deque<int> qmax;  // 递减队列std::deque<int> qmin;  // 递增队列int res = 0;int i = 0;  // 区间左边界int j = 0;  // 区间右边界int N = arr.size();while (i < N) {while (j < N) {while (!qmax.empty() && arr[qmax.back()] <= arr[j]) {qmax.pop_back();  // 维护递减队列性质}qmax.push_back(j);while (!qmin.empty() && arr[qmin.back()] >= arr[j]) {qmin.pop_back();  // 维护递增队列性质 }qmin.push_back(j);if (qmax.front() - qmin.front() >= num) {break;  // 找到满足性质的区间,不再需要递增区间右边界范围}++j;}if (qmax.front() == i) {qmax.pop_front();  // 以arr[i]为边界的区间查找结束}if (qmin.front() == i) {qmin.pop_front();}res += (j - i);  // arr[i]为边界的个数i++;  // 移动窗口右边界}
}int main() {return 0;
}

参考文献

  • https://endlesslethe.com/monotone-queue-and-stack-tutorial.html
  • https://cloud.tencent.com/developer/article/1109268
  • https://blog.csdn.net/Dacc123/article/details/50545577

单调栈和单调队列的应用即总结相关推荐

  1. 【数据结构】单调栈和单调队列 详解+例题剖析

    算法:单调栈和单调队列 一.单调栈和单调队列 二.单调栈例题 1.模板题入门 2.不懂不要急,看这道题 三.单调队列例题 1.入门 2.进阶 一.单调栈和单调队列 单调栈和单调队列与普通的栈,队列不同 ...

  2. 刷题关键点总结-单调栈、单调队列

    单调栈,单调队列顾名思义,栈内或者队列内元素具有一致下降或者一致上升的特点. 选用单调栈或者队列的出发点: 使用单调栈或者单调队列一般具有三个出发点: 1.求栈或队列内某个元素左侧或者右侧第一个比它大 ...

  3. 单调栈与单调队列简单例题

    单调栈与单调队列简单例题 单调栈: POJ3250 题意:有n只奶牛排成一列向右看,每头奶牛只能看到比自己矮的奶牛,即会被高的奶牛挡住后面,问共有多少只奶牛能被看到 思路:考虑每头奶牛能被前面牛看到的 ...

  4. 模板_单调栈_AcWing_830. 单调栈_底顶递增栈

    模板_单调栈_AcWing_830. 单调栈_底顶递增栈 830. 单调栈 题目 提交记录 讨论 题解 视频讲解 给定一个长度为 NN 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 − ...

  5. 数据结构 - 单调栈、单调队列

    单调栈:每日温度 请根据每日 气温 列表 temperatures ,请计算在每一天需要等几天才会有更高的温度.如果气温在这之后都不会升高,请在该位置用 0 来代替 单调栈基本只处理NGE问题(Nex ...

  6. 线性结构 —— 单调栈与单调队列

    [单调栈] 1.原理 单调栈,就是栈内元素保持一定单调性(单调递增或单调递减)的栈,即从栈底到栈顶单调递增或递减. 对于单调递增的栈,如果栈为空或入栈元素值大于等于栈顶元素值,则入栈:否则,若入栈会破 ...

  7. 单调栈是单调递增还是单调递减?

    一言以蔽之,最后的需求是什么,单调栈的单调性就是什么. 如果要找第一个小于的元素,单调栈即为递增:找第一个大于的元素,单调栈即为递减.当前元素与目标元素的大小关系就是单调栈内部元素的大小关系.

  8. 【c++刷题Day2】专题3栈与队列单调栈与单调队列T3

    这是C++刷题的Day2

  9. 数据结构录 之 单调队列单调栈。

    队列和栈是很常见的应用,大部分算法中都能见到他们的影子. 而单纯的队列和栈经常不能满足需求,所以需要一些很神奇的队列和栈的扩展. 其中最出名的应该是优先队列吧我觉得,然后还有两种比较小众的扩展就是单调 ...

最新文章

  1. python scrapy 入门,10分钟完成一个爬虫
  2. linux 挂载光盘映像,在 Windows Mac和Linux上,如何挂载iso和其他光盘映像
  3. C#种死锁:事务(进程 ID 112)与另一个进程被死锁在 锁
  4. 麻省理工Hadi Salman新作:ViT架构可以有效抵御图像补丁攻击
  5. WeChat授权Token验证体系:用token来隐藏微信用户的openid不用用户名与密码了, 与cookie与session很相似
  6. HBase写性能优化策略
  7. 华为获 25 份 5G 合同;ofo 退款用户数超千万;贾跃亭躲豪宅拒收法律文书 | 极客头条...
  8. centos 7 下使用dvorak键盘布局
  9. Docker部署项目的步骤,按步骤一步一步来,一切都会成功
  10. 7、sudo权限问题考察一题(考试题答案系列)
  11. mysql导出sql和表格文件大小_atitit.sql server2008导出导入数据库大的表格文件... oracle mysql...
  12. 数据结构与算法python语言描述第三章课后答案_《数据结构与算法Python语言描述》习题第二章第三题(python版)...
  13. CSDN用户数据库泄露事件给我们的启示
  14. win7 计算机定时关机脚本,win7定时关机命令是什么 如何设置定时关机【图解】...
  15. 20210219 plecs为不同的PWM 配置不同的死区时间
  16. 孙玄:分布式系统选主怎么玩
  17. 微软Exchange Server 0Day漏洞,尽快修复
  18. 朝花夕拾-不务正业的大学生做了什么比赛?
  19. 首先,打破一切常规 学习笔记 之二
  20. 如何查询网络出口IP

热门文章

  1. 补码乘法实验原理_你真的理解补码吗?
  2. selenium python怎么断言_如何为python / selenium中的特定元素断言文本?
  3. 计算机算法设计与分析 最大子段和问题
  4. Vivado设计流程(三)功能仿真
  5. Windows程序开发——指挥官夏尔对于Windows程序开发框架的选择
  6. np.memmap读取大文件
  7. 好用好玩的Python包
  8. 东大OJ-1544: GG的战争法则
  9. leetcode978. Longest Turbulent Subarray
  10. [SHOI2015] 超能粒子炮·改