1.单调栈的定义
单调栈指的是存储一些数据到栈中,同时这些数据满足单调递增或是单调递减
2.单调栈的作用
给定一个序列,指定一个序列中的元素,求解该元素 左侧/右侧 第一个比自身小/大的元素。
3.根据题目来理解单调栈
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
解题思路:
首先.看到目的为求矩形的最大面积,因此先分析面积的求法.最简单的就是底x高
分析高:
随便取一根柱子,求以它为高的面积最大的矩形时,肯定是越宽越好,则将它向左右延伸,观察最远距
离.
例如,下标为2的柱子只能向右延伸到下标为3的柱子,原因是下标为4和下标为1的柱子高度低于下标
为2的柱子.而下标为3的柱子则无法左右延伸,原因同样是左右的柱子都低于自己
由此可得找到左右第一个小于自己的下标即可得出以当前柱子高度为高的矩形最大面积
接下来尝试求左边第一个小于自己高度的柱子下标
从上图可以得出一个结论,若有一个柱子下标为a,同时height[a]>=height[a+1],则a后面的柱子将不会
以a为边界,原因是在a之后有个更小的将其挡住
例如下标为0的柱子之后的柱子不可能以它为边界,因为会被下标为1的柱子限制高度为1
可以想出用一个数据结构来存放下标,以便能够得出左右第一个小于自己下标
右边的第一个小很容易得出,若在j
位置,j+1
比他低,则就是右边第一个小于自己的下标
左边的第一个小若是保留单调性,则j-1
便是左边第一个小于自己的下标
假设我们用一个单调数组来存放数据
index[1,2,3]的柱子是1,5,6的高度,遇到下标4
时,高度为6的柱子右边为第一个小,左边为第一个小,所
以目前高度为6的柱子最大面积为6 * 1
这时下标为2的柱子由于单调递增所以可以向右延伸一格,再与下标为4
比较,发现是右边第一个小,同
理高度为5的柱子最大面积为5 * 2
下标为1的柱子却比下标为4
的柱子低,所以还没到他的右边第一个小,保持不变
看到这里可以发现正符合栈的特性,先进后出,又由于单调性,所以使用单调栈
三种特殊情况(强烈建议画画,会更加易懂):
- 正如上方例子,遍历到最后会留下高度为[1,2,3]柱子,这时可以假设最右边有个高度为0的柱子,遍历完数组后,若栈不为空则重新执行一遍逻辑
- 栈中仅留一个元素,此时栈中柱子为之前的最小高度,因此可以扩散到当前下标的前一个位置
- 可能会有相同高度的柱子,会发现并不影响计算,最左边的相同高度柱子一样会给出正确答案
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return heights[0];
}
int res = 0;
Deque<Integer> stack = new ArrayDeque<>(len);
for (int i = 0; i < len; i++) {
// 这个 while 很关键,因为有可能不止一个柱形的最大宽度可以被计算出来
while (!stack.isEmpty() && heights[i] < heights[stack.peekLast()]) {
int curHeight = heights[stack.pollLast()];
//用于解决特殊情况3
while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
stack.pollLast();
}
int curWidth;
//用于解决特殊情况2
if (stack.isEmpty()) {
curWidth = i;
} else {
curWidth = i - stack.peekLast() - 1;
}
// System.out.println("curIndex = " + curIndex + " " + curHeight * curWidth);
res = Math.max(res, curHeight * curWidth);
}
stack.addLast(i);
}
//用于解决特殊情况1
while (!stack.isEmpty()) {
int curHeight = heights[stack.pollLast()];
while (!stack.isEmpty() && heights[stack.peekLast()] == curHeight) {
stack.pollLast();
}
int curWidth;
if (stack.isEmpty()) {
curWidth = len;
} else {
curWidth = len - stack.peekLast() - 1;
}
res = Math.max(res, curHeight * curWidth);
}
return res;
}
}
4.单调栈总结
显然是有四种情况的:
1:向左找第一个比自身大的数。
2:向左找第一个比自身小的数。
3:向右找第一个比自身大的数。
4:向右找第一个比自身小的数。