一、题目
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
输入: [2,1,5,6,2,3]
输出: 10
二、思路及代码实现
思路一:暴力法(超出时间限制)
两个柱子之间的矩形面积由它们之间较矮的那个柱子决定。因此,我们可以考虑所有两两柱子之间的矩形面积,长即为两柱子之间较矮的那一个,宽即为两柱子之间的距离,最后找出最大值即可。
参考代码:
class Solution {
public int largestRectangleArea(int[] heights) {
int maxArea = 0;
for(int i = 0; i < heights.length; i++){
for(int j = i; j < heights.length; j++){
// 找到i、j之间最矮的柱子,求出面积
int minHeight = Integer.MAX_VALUE;
for(int k = i; k <= j; k++){
minHeight = Math.min(minHeight, heights[k]);
}
maxArea = Math.max(maxArea, minHeight * (j - i + 1));
}
}
return maxArea;
}
}
思路二:单调栈
前一种思路是考虑两个柱子之间能组成的最大面积,换一种思路,考虑每一个柱子,以当前柱子的高度为矩形的长,然后向左、向右延伸,看能够勾勒出的最大的矩形是多少。
第 i 个柱子能组成的最大矩形面积是多少?
是以柱子 i 为中心,向左找到第一个小于
h
e
i
g
h
t
s
[
i
]
heights[i]
heights[i] 的柱子 left,向右找到第一个小于
h
e
i
g
h
t
s
[
i
]
heights[i]
heights[i] 的柱子 right,则矩形的面积为
h
e
i
g
h
t
s
[
i
]
×
(
r
i
g
h
t
−
l
e
f
t
−
1
)
heights[i] \times (right - left - 1)
heights[i]×(right−left−1) 如下图,
柱子 i 勾勒成的最大矩形的面积为
h
e
i
g
h
t
s
[
i
]
×
(
r
i
g
h
t
−
l
e
f
t
−
1
)
=
3
×
(
5
−
1
−
1
)
=
9
heights[i] \times (right - left - 1) = 3 \times (5 - 1 - 1) = 9
heights[i]×(right−left−1)=3×(5−1−1)=9 那么,我们要解决的就是怎么找柱子 i 左右两边第一个小于
h
e
i
g
h
t
s
[
i
]
heights[i]
heights[i] 的柱子。
利用单调递减栈找柱子 i 左右两边第一个小于 h e i g h t s [ i ] heights[i] heights[i] 的柱子:
单调栈的知识可以参考 Java数据结构与算法——单调栈算法笔记。
以题目示例为例(入栈的是索引,这里为了看的更直观,把元素值标注在括号内),
- 从左往右依次入栈;
- 刚开始栈为空,2 入栈,得到
|0(2)| - 下一个元素 1 < 2,2 出栈,1入栈,得到
|1(1)| - 下一个元素 5 > 1,5 直接入栈,得到
|2(5)|
|1(1)| - 下一个元素 6 > 5,6 直接入栈,得到
|3(6)|
|2(5)|
|1(1)| - 下一个元素 2 < 6,6 出栈;2 < 5,5 继续出栈;2 > 1 ,2 入栈,得到
|4(2)|
|1(1)| - 下一个元素 3 > 2,3 直接入栈,得到
|5(3)|
|4(2)|
|1(1)|
可以看到,每个阶段栈内的元素,栈顶元素底下的那个元素,就是该栈顶元素左边第一个小于该栈顶元素的元素。换句话说,当前栈顶元素弹出后,新的栈顶元素就是弹出元素的左边的第一个小于弹出元素的元素。另外,对于当前要入栈的元素 e,如果需要先弹出栈顶元素才能入栈,说明 e 是栈顶元素右边第一个小于该栈顶元素的元素。
举个例子,我们看看第二个 2 入栈时的情况:此时栈顶元素为 6,2 < 6,要先弹出 6 才能将 2 入栈,说明 2 是 6 右边第一个小于 6 的元素;6 出栈后,新的栈顶元素 5 是 6 左边第一个小于 6 的元素。
参考代码:
class Solution {
public int largestRectangleArea(int[] heights) {
int maxArea = 0;
int n = heights.length;
LinkedList<Integer> stack = new LinkedList<>();
// 为了方便,我们给最左边和最右边加上两个高度为 0 的柱子,这样方便计算第 0 个和第 n - 1 个柱子
int[] new_heights = new int[n + 2];
// 索引 1 ~ n 是原来的柱子
for(int i = 1; i < n + 1; i++){
new_heights[i] = heights[i - 1];
}
// 计算每个柱子组成矩形的最大面积
for(int i = 0; i < n + 2; i++){
while(!stack.isEmpty() && new_heights[stack.peek()] > new_heights[i]){
int cur = stack.pop();
maxArea = Math.max(maxArea, new_heights[cur] * (i - stack.peek() - 1));
}
stack.push(i);
}
return maxArea;
}
}
Tips:
单调栈特别适合解决那些,两头大小决定中间值的问题。