3.1 单调栈
单调栈
定义
- 单调栈中存放的数据应该是有序的,所以单调栈也分为单调递增栈和单调递减栈
- 单调递增栈:单调递增栈就是从栈底到栈顶数据是从小到大
- 单调递减栈:单调递减栈就是从栈底到栈顶数据是从大到小
规则
- (单调递减栈)现在有一组数10,3,7,4,12。从左到右依次入栈,则如果栈为空或入栈元素值小于栈顶元素值,则入栈;否则,如果入栈则会破坏栈的单调性,则需要把比入栈元素小的元素全部出栈。
- 0入栈时,栈为空,直接入栈,栈内元素为10。
- 入栈时,栈顶元素10比3大,则入栈,栈内元素为10,3。
- 入栈时,栈顶元素3比7小,则栈顶元素出栈,此时栈顶元素为10,比7大,则7入栈,栈内元素为10,7。
- 入栈时,栈顶元素7比4大,则入栈,栈内元素为10,7,4。
- 2入栈时,栈顶元素4比12小,4出栈,此时栈顶元素为7,仍比12小,栈顶元素7继续出栈,此时栈顶元素为10,仍比12小,10出栈,此时栈为空,12入栈,栈内元素为12。
应用
局部顺序问题,可以使用单调栈解决。例如存在一个局部增序的序列。需要找到所有的局部增序的序列。则单调增栈的每次连续入栈,都是增序。每次连续出栈都是违反增序规则的。
单调递减栈:
- 在一个队列中针对每一个元素从它右边寻找第一个比它大的元素
- 在一个队列中针对每一个元素从它左边寻找第一个比它大的元素(从后往前遍历)
可以以 O(1) 的时间复杂度得知某个位置左右两侧比他大(或小)的数的位置,当你需要高效率获取某个位置左右两侧比他大(或小)的数的位置的的时候就可以用到单调栈。
- 求解数组中元素右边第一个比它小的元素的下标,从前往后,构造单调递增栈;比它小的元素会将其pop掉。
- 求解数组中元素右边第一个比它大的元素的下标,从前往后,构造单调递减栈;比它大的元素会将其pop掉。
- 求解数组中元素左边第一个比它大的元素的下标,从后往前,构造单调递减栈;比他大的元素会将其pop掉
- 求解数组中元素左边第一个比它小的元素的下标,从后往前,构造单调递增栈。比他小的元素会将其pop掉。
代码
- 单调递增栈
1 | for(int i = 0; i < T.size(); i++){ |
- 单调递减站
1 | for(int i = T.size() - 1; i >= 0; i--){ |
特性
单调栈的关键不在于栈的顺序,不在于是递增栈还是递减栈。
- 出栈入栈的时候所处的状态。
- 出栈入栈的时候所采取操作。
以递减栈为例。
- 当元素出栈的时候,说明后续有更大的元素,栈底的元素也比自己大。所以出栈元素是倒数第二大的元素。
- 当元素入栈的时候,之前的元素都比自己大的状态。并不知道正向是第几大的元素。
递增栈则相反
- 当前元素出栈的时候,说明当前元素是倒数第二小的元素。
问题总结
一般会采取怎样的操作呢?具体再总结。
- 视野总和:利用了栈内元素的数量计算问题。
- 柱形图中的最大矩形:利用的是出栈序列。这组序列构成了可能的最大矩形。
- 接雨水问题:单调栈解法也是利用了出栈序列。这个序列的左右边界能够形成一个容器。
- 接雨水问题2
其它类型的单调栈
总结
综上所述主要有以上三种利用思想。
- 针对栈内元素数量的利用。第一个问题,仅仅是简单的模拟。
- 针对有序性的利用。每次都是贪心的求得最大的3,反向求解第二大的元素。
- 针对出栈序列的利用。在递增栈和递减栈中,每一个出栈序列的两端,都是一个最大边界、或者最小边界,在边界中会形成问题的一个解序列。序列中的每个值,比两个边界大或者小。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Estom的博客!










