栈(Stack)是一种操作受限的线性表,只允许一端进,同一端出,因而具有后进先出(LIFO)的特性。
单调栈(Monotonic Stack)是一种特殊的栈,它首先是一个栈,其次栈中的所有元素单调递增或者单调递减。
单调栈在算法中的应用在于它能够在一次扫描即O(n)的复杂度之内找到数组中每一个元素的前上界(单增栈)或者前下界(单减栈)。
// 递增栈
for(int i=0; i<arr.length; i++) {
while(!stack.empty() && stack.peek() >= arr[i]) {
stack.pop();
}
stack.push(arr[i]);
}
// 递减栈
for(int i=0; i<arr.length; i++) {
while(!stack.empty() && stack.peek() <= arr[i]) {
stack.pop();
}
stack.push(arr[i]);
}在上面的单调栈中,访问到任一个数组元素时,当前栈顶就是该元素的前上界(递增栈)或者前下界(递减栈)。
下面以实际算法题来说明递增栈的作用。
链接:https://www.nowcoder.com/question/next?pid=21283868&qid=830860&tid=37511217 来源:牛客网
时间限制:C/C++ 2秒,其他语言4秒空间限制:C/C++ 256M,其他语言512M 小Q在周末的时候和他的小伙伴来到大城市逛街,一条步行街上有很多高楼,共有n座高楼排成一行。 小Q从第一栋一直走到了最后一栋,小Q从来都没有见到这么多的楼,所以他想知道他在每栋楼的位置处能看到多少栋楼呢?(当前面的楼的高度大于等于后面的楼时,后面的楼将被挡住) 输入描述: 输入第一行将包含一个数字n,代表楼的栋数,接下来的一行将包含n个数字wi(1<=i<=n),代表每一栋楼的高度。 1<=n<=100000; 1<=wi<=100000; 输出描述: 输出一行,包含空格分割的n个数字vi,分别代表小Q在第i栋楼时能看到的楼的数量。 示例1 输入 6 5 3 8 3 2 5 输出 3 3 5 4 4 4 说明 当小Q处于位置3时,他可以向前看到位置2,1处的楼,向后看到位置4,6处的楼,加上第3栋楼,共可看到5栋楼。当小Q处于位置4时,他可以向前看到位置3处的楼,向后看到位置5,6处的楼,加上第4栋楼,共可看到4栋楼。
// 递减栈 栈中元素数目即为能看到的楼数目
// 两个栈 一个表示向左能看到的数目 一个表示向右 注意所有栈都不考虑当前所在楼本身 因此最后结果要加1
// 以向右看为例
// 单调栈里维护了从最左边到该位置前递减序列 而到达当前位置的递减序列对于当前位置来 都是可见的
// 因此单调栈的大小保存了能看到楼的个数
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Stack;
public class Tencent2020backend2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] height = new int[n];
for(int i=0; i<n; i++) {
height[i] = sc.nextInt();
}
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
ArrayList<Integer> count1 = new ArrayList<>();
ArrayList<Integer> count2 = new ArrayList<>();
for(int i = 0, j = height.length-1; i < height.length && j>=0; i++, j--) {
count1.add(stack1.size());
count2.add(0, stack2.size());
while(!stack1.empty() && stack1.peek() <= height[i]) stack1.pop();
while(!stack2.empty() && stack2.peek() <= height[j]) stack2.pop();
stack1.push(height[i]);
stack2.push(height[j]);
}
for(int i=0; i<n; i++) {
System.out.print(count1.get(i) + count2.get(i) + 1 + " ");
}
}
}给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。

以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。

图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。 示例: 输入: [2,1,5,6,2,3] 输出: 10
我下面给出的解法比官方题解还要简洁,注意栈中存的是索引而不是元素,因为要作为宽度计算面积:
# 递增栈
# 每次遇到非递增元素 可以计算一次面积 在所有计算的面积中找到最大的即可
# 巧妙的利用了递增栈的性质
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
stack = [-1]
heights.append(-1)
res = 0
for idx, val in enumerate(heights):
while heights[stack[-1]] > val:
h = heights[stack.pop()]
res = max(res, h*(idx - stack[-1]-1)) # idx-stack[-1]-1是当前递增序列的宽度
stack.append(idx)
return res