题解
- 题意:给定数组
heights[]
,包含n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积,矩形中间不能为空,样例,给定数组heights = [2,1,5,6,2,3]
, 输出10
,即阴影部分的面积:
- 题解:首先考虑什么情况下可能围成最高的矩形?自然是要么够宽,要么够高,但是这两个并没有依赖性,因此需要共同考虑,而暴力做法在有两个自由变量的时候,复杂度都是 O ( n 2 ) O(n^2) O(n2),不合适,我们要考虑如何减少没必要的状态。
- 这题中柱子的高度是一个非常重要的变量,因为无论多个柱子形成的形状是
凹
形还是凸
形,围成矩形的高都是由中间最低的柱子高度决定的,当我们确定高度的时候,最大矩形的面积就只和宽度相关。-
我们可以考虑扩散思想:以当前柱子为最低高度,向左右扩散,直到遇到比当前高度低的柱子,就停止扩散,那么这样就可以得到以当前柱子高度为最小值,使用两个数组
left[i]
,right[i]
来记录当前柱子的最左下标和最右下标 -
得到所有柱子所能扩散得到的最大面积之后,最后再进行一个循环遍历,就可以获得最大的矩形面积
-
不幸的是,如果用
i--
和i++
来进行左边和右边的扩散,那么时间复杂度还是 O ( n 2 ) O(n^2) O(n2),什么情况下一些状态(柱子)是可以不用重复遍历的呢? -
减少状态: 其实对于每个柱子而言,我们只需要知道左边和右边第一个比当前柱子小的地方在哪里即可。
-
单调栈记录左边最近较小值:单调(递增)栈的性质恰好满足这个特点,因为对于
i
个元素,单调栈中存放的是能够记录在第i
个位置之前保留单调性的元素。换句话说,为了保留单调递增的特点,单调栈会抛弃不满足递增特性的元素,也就是当遍历到第i
个元素的时候,单调栈会将比i
高的元素都pop
出去,这样一来,栈中与i
相邻的柱子就是距离i
最近的较低柱子。 -
右边最近较小值:找到右边最近较小值没有任何难度,
i++
往后遍历的时候,自然就能找到,如果找不到,说明当前柱子就是全局最矮的柱子。 -
特殊情况:对于全局最矮的柱子,我们会发现,左边会
pop
空,右边会超过n
,那么为了防止越界的情况,我们需要给头部和尾部添加一个最小元素。
-
- 实现:
class Solution {
public int largestRectangleArea(int[] heights) {
Stack<Integer> s1 = new Stack<Integer>();
int[] new_heights = new int[heights.length + 1];
for (int i = 1; i < heights.length + 1; i++) new_heights[i] = heights[i - 1];
int n = new_heights.length;
int max_size = 0;
for(int i = 0;i < n;i++){
while(!s1.empty() && new_heights[s1.peek()] > new_heights[i]){
int curh = new_heights[s1.peek()];
s1.pop();
int l = s1.peek()+1;
int r = i-1;
max_size = Math.max(max_size, (r-l+1)*curh);
}
s1.push(i);
}
return max_size;
}
}
- 更直观的实现(类似单调栈的思路,但是用),来自Leetcode题解
class Solution {
public int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) return 0;
int n = heights.length;
int[] left_i = new int[n];
int[] right_i = new int[n];
left_i[0] = -1;
right_i[n - 1] = n;
int res = 0;
for (int i = 1; i < n; i++) {
int tmp = i - 1;
while (tmp >= 0 && heights[tmp] >= heights[i]) tmp = left_i[tmp];
left_i[i] = tmp;
}
for (int i = n - 2; i >= 0; i--) {
int tmp = i + 1;
while (tmp < n && heights[tmp] >= heights[i]) tmp = right_i[tmp];
right_i[i] = tmp;
}
for (int i = 0; i < n; i++) res = Math.max(res, (right_i[i] - left_i[i] - 1) * heights[i]);
return res;
}
}