给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
题解
思路:找两边第一个小于它的值(单调递增栈)
首先,要想找到第 i 位置最大面积是什么?
是以i 为中心,向左找第一个小于 heights[i] 的位置 left_i;向右找第一个小于于 heights[i] 的位置 right_i,即最大面积为 heights[i] * (right_i - left_i -1), 如下图所示:
所以,我们的问题就变成如何找 right_i 和 left_i?
最简单的思路就是,就是暴力法,直接分别在 i 左右移动,但时间复杂度为O(N^2)
思路二:栈
利用单调栈
维护一个单调递增的栈,就可以找到 left_i 和 right_i。
但还需要考虑两种特殊的情况:
弹栈的时候,栈为空;
遍历完成以后,栈中还有元素;
为此可以我们可以在输入数组的两端加上两个高度为 0 (或者是 0.5,只要比 1 严格小都行)的柱形,可以回避上面这两种分类讨论。
这两个站在两边的柱形有一个很形象的名词,叫做哨兵(Sentinel)。
有了这两个柱形:
左边的柱形(第 1 个柱形)由于它一定比输入数组里任何一个元素小,它肯定不会出栈,因此栈一定不会为空;
右边的柱形(第 2 个柱形)也正是因为它一定比输入数组里任何一个元素小,它会让所有输入数组里的元素出栈(第 1 个哨兵元素除外)。
这里栈对应到高度,呈单调增加不减的形态,因此称为单调栈(Monotone Stack)。它是暴力解法的优化,计算每个柱形的高度对应的最大矩形的顺序由出栈顺序决定。
class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
if (len == 0) {
return 0;
}
if (len == 1) {
return heights[0];
}
int[] newHeight = new int[len+2];
//哨兵 避免栈空操作
newHeight[0]=0;
System.arraycopy(heights,0,newHeight,1,len);
//哨兵,使得数组最后几个元素也可以被弹出,不需要特殊处理
newHeight[len+1]=0;
Stack<Integer> stack = new Stack<>();
//提前将newHeight中第一个元素(哨兵)入栈,避免栈空无法进行stack.peek()操作
stack.push(0);
int area = 0;
for(int i=1;i<len+2;i++){
while(!stack.isEmpty()&&newHeight[i]<newHeight[stack.peek()]){
int cur = stack.pop();
//以cur所在矩形为最大高度的矩形,左边界是stack.peek(),右边界是i-1
area=Math.max(area,newHeight[cur]*(i-stack.peek()-1));
}
stack.push(i);
}
return area;
}
}