要求:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
输入: [2,1,5,6,2,3]
输出: 10
思路:
暴力解法:
- 往左边找,找到大于等于当前柱形高度的最左边元素的下标。
- 往右边找,找到大于等于当前柱形高度的最右边元素的下标。
- 转自https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/bao-li-jie-fa-zhan-by-liweiwei1419/
public class Solution {
public int largestRectangleArea(int[] heights) {
int len = heights.length;
// 特判
if (len == 0) {
return 0;
}
int res = 0;
for (int i = 0; i < len; i++) {
// 找左边最后 1 个大于等于 heights[i] 的下标
int left = i;
int curHeight = heights[i];
while (left > 0 && heights[left - 1] >= curHeight) {
left--;
}
// 找右边最后 1 个大于等于 heights[i] 的索引
int right = i;
while (right < len - 1 && heights[right + 1] >= curHeight) {
right++;
}
int width = right - left + 1;
res = Math.max(res, width * curHeight);
}
return res;
}
}
利用单调栈的做法:
按顺序遍历数组高度,例如以[2, 1, 5, 6, 2, 3]为例。
- 对于2,往右遍历一位,1就比2小,所以以2为高度的矩形的右边界就已经限定了,但是2的左边界还不确定。为了可以确定左边界,我们添加一个0先进去,称为左侧哨兵。值只需要比数组中任意一个元素小即可。这样2的矩形面积就确定了。
- 对于1,左侧边界确定了,就是左侧哨兵,因为2比1大,所以2无法封锁1的左侧边界,1这时候需要寻找他的右侧边界。5比1大,6比1大,2比1大,3比1大,所以以1为高度的矩形的宽是整个数组的长度。
- 依次类推,遇见比当前元素大的,都是矩形宽度加一,遇见比当前元素小的,说明要跳出来计算边界。
- 以上的步骤和暴力解法并无差异,妙就妙在使用栈对元素进行存储,前后还加了两个哨兵,对元素的边界进行限界。
**[注意:]**在缓存数据的时候,从左向右缓存,在计算结果的时候是从右往左计算,而且计算完成之后不需要再进栈,符合后进先出的特点。
- 每次进栈的是索引,元素在数组中的索引。
- 新建一个数组存储元素,需要一头一尾加两个哨兵元素,中间的元素全部由原数组复制进来。
- 比较的时候不需要弹出,调用peek()即可,读取元素值的时候需要将元素弹出,调用poll()。
代码:
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;
int[] nums = new int[len+2];
nums[0] = 0;
System.arraycopy(heights, 0, nums, 1, len);
nums[len+1] = 0;
len = len + 2;
heights = nums;
Deque<Integre> stack = new ArrayDeque<>(len);
stack.addLast(0);
for (int i = 0; i < len: i++) {
while (heights[i] < heights[stack.peekLast()]) {
int cur = heights[stack.pollLast()];
int wid = i - stack.peekLast() - 1;
res = Math.max(res, cur * wid);
}
stack.addLast(i);
}
return res;
}
}