柱状图中最大的矩形 - 单调栈
1.题目描述
给定n
个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为1
。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
输入: [2,1,5,6,2,3]
输出: 10
2.题解
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Stack<Integer> mono_stack = new Stack<Integer>();
for (int i = 0; i < n; ++i) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
mono_stack.pop();
}
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
mono_stack.push(i);
}
mono_stack.clear();
for (int i = n - 1; i >= 0; --i) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
mono_stack.pop();
}
right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
遍历每根柱子,这根柱子作为所能形成的最大矩形的一部分,计算该矩形的面积。
该矩形两边的柱子高度肯定都小于矩形内的所有柱子的高度。
于是,需要计算每根柱子的左右边界。值得注意的是,这里的第一根柱子的左边界为-1
,最后一根柱子的右边界为n
(这里是柱子个数)。我们称这里的-1
和n
为哨兵,它们用于处理边界值的问题。
以[2,1,5,6,2,3]
为例:
遍历第一根柱子,其左边界为-1
;
遍历第二根柱子,由于其高度小于等于第一根柱子的高度,所以第二根柱子的左边界为-1
;
遍历第三根柱子,由于其高度大于第二根柱子的高度,所以第三根柱子的左边界为第二根柱子所在的位置1
。
如果每根柱子的高度单调递增,就将这些柱子(位置)压入栈中。当柱子i
的高度小于前一根柱子的高度,前一根柱子出栈,只要栈中的柱子高度大于等于柱子i
的高度,就依次出栈,直到栈中的柱子高度小于柱子i
的高度或者栈为空。这样就可以找到每根柱子的左边界。
注:这种方法跟接雨水的方法二很相似。
只遍历一次就确定柱子的左右边界的方法:
public int largestRectangleArea(int[] heights) {
int n = heights.length;
int[] left = new int[n];
int[] right = new int[n];
Arrays.fill(right, n);
Stack<Integer> mono_stack = new Stack<Integer>();
for (int i = 0; i < n; ++i) {
while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
right[mono_stack.peek()] = i;
mono_stack.pop();
}
left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
mono_stack.push(i);
}
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
}
return ans;
}
在确定柱子i
的左边界时,栈中的柱子可能出栈,注意到这些出栈的柱子的右边界就是柱子i
的位置。
需要注意的是柱子出栈的条件是heights[mono_stack.peek()] >= heights[i]
,以[2,1,5,5,6,2,3]
为例,计算出的right
数组为[1,7,3,5,5,7,7]
,注意到这里计算得到的第三根柱子的右边界是3
,而正确的右边界应该是5
。但是这不影响计算矩形的最大面积。
因为柱子出栈后,也会把当前遍历的柱子压入栈中,这样计算出的矩形最右边那根柱子的右边界肯定是正确的。
这里用第四根柱子计算出最大矩形的面积,而这个最大矩形的最右边那根柱子是第五跟柱子。
因为遍历第五根柱子时,第四根柱子没有出栈,这样在遍历第六根柱子时,第五根柱子和第四根柱子依次出栈,所以这里第四根柱子和第五根柱子的右边界都是正确的。
注意到第五根柱子的左边界不是最大矩形的左边界,所以这里只能用第四根柱子计算最大矩形的面积。
这里的关键是找到最大矩形中的某根柱子,该柱子的左右边界正好是最大矩形的左右边界。
参考: