给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]
。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。
示例:
输入: [2,1,5,6,2,3]
输出: 10
思路与代码
暴力破解法
暴力法应该比较容易想到,以每一个矩形为中心,向左向右搜索能到达的最远边界,计算以当前矩形高度的最大矩形面积。(中心扩展法)
单调栈法
其实前面暴力解法的就是确定以每一个矩形高度能向左右扩展到多大。那么我们能不能在一次遍历就确定呢,其实是可以得,我们利用栈来辅助完成。
从左向右遍历时,我们不知道当前矩形向右还能扩展到何处,因为我们还不知道右边矩形的高度,但是我们是从左边遍历过来的,是否能记录左边能扩展到何处呢。
我们就拿示例的数组 [2, 1, 5, 6, 2, 3]
为例:
1、一开始看到的柱形高度为 2 ,这个时候以这个 2 为高度的最大面积的矩形还不能确定,我们需要继续向右遍历,如下图。
2、然后看到到高度为 1 的柱形,这个时候以这个柱形为高度的矩形的最大面积还是不知道的。但是它之前的以 2 为高度的最大面积的矩形是可以确定的,这是因为这个 1 比 2 小 ,因为这个 1 卡在了这里 2 不能再向右边扩展了,如下图。
我们计算一下以 2 为高度的最大矩形的面积是 2。其实这个时候,求解这个问题的思路其实已经慢慢打开了。如果已经确定了一个柱形的高度,我们可以无视它,将它以虚框表示,如下图。
看到这里,我们大概理一下思路:对于当前矩形,因为我们无法确定其右边界,所以它的最大矩形是不确定,我们把不确定的矩形先放入栈中。然而当前矩形可能就是左边矩形的右边界,所以我们需要做个判断,将当前矩形高度和栈顶矩形高度作个对比:
① 当前矩形高度 > 栈顶矩形。 那么当前矩形可能不是左边矩形的右边界,所以直接把当前矩形入栈即可。
② 当前矩形高度 < 栈顶矩形。 当前矩形肯定是栈顶矩形右边界,那么就可以确定栈顶矩形的最大矩形情况。取出栈顶矩形,右边界为当前矩形,左边界为取出栈顶矩形后的栈顶矩形
③ 当前矩形高度 = 栈顶矩形。 当前矩形可能不是栈顶矩形右边界,当前矩形和栈顶矩形肯定是连在一起的,两者的最大扩展矩形肯定是一起的,所以直接用当前矩形去覆盖栈顶矩形就行,没必要确定两次最大扩展矩形。但为什么要用当前矩形去代替栈顶矩形,而不是用栈顶矩形代替当前矩形,因为后面比当前矩形高的矩形的左边界肯定是当前矩形,而不是栈顶矩形。
为什么该方法叫单调栈法
,因为栈中存放的矩形高度是单调递增的,栈顶矩形的高度最高。栈中存放的矩形都是还未能确定最大扩展矩形的,能确定的矩形都已经被弹出栈了。
class Solution {
public int largestRectangleArea(int[] heights) {
Deque<Integer[]> stack = new ArrayDeque();
int max = 0;
stack.push(new Integer[]{-1, -1});
for (int i = 0; i < heights.length; i++) {
if (heights[i] > stack.peek()[0]){
// 如果当前矩形的高度大于前一个矩形高度
}else if (heights[i] < stack.peek()[0]){
// 如果当前矩形的高度小于前一个矩形高度
while (heights[i] < stack.peek()[0]) {
Integer[] lastRect = stack.pop();
max = Math.max(max, lastRect[0] * (i - 1 - stack.peek()[1]));
}
}else{
stack.pop();
}
stack.push(new Integer[]{heights[i], i});
}
while (stack.size() > 1) {
Integer[] rect = stack.pop();
max = Math.max(max, rect[0] * (heights.length - 1 - stack.peek()[1]));
}
return max;
}
}