看了官方的视频后,才得知该题是一道很经典的题目了。官方推荐首先采用暴力解法,然后再优化为单调栈。
暴力解法
要求矩形的面积,首先要知道长和宽(这道题由于是在直方图中,所以下面称为宽和高),所以有两种办法,第一种是固定宽,然后找高;第二种是固定高,然后找宽。如果采用固定宽,那么会导致O(
n
3
n^3
n3)的复杂度。如果采用固定高,会有O(
n
2
n^2
n2)的复杂度。官方说通过Java提交暴力解法也可以通过测试,而且暴力解法有助于了解一些细节。
具体思路就是,遍历每一个元素,当前元素为高,然后依次往两边遍历,直到两边的高度都比当前高度低或者是到达边界。
class Solution1 {
public int largestRectangleArea(int[] heights) {
int aws = 0, tmp;
int p, q;
for (int i = 0; i < heights.length; i++) {
p = q = i;
while (p > 0 && heights[p - 1] >= heights[i]) {
--p;
}
while (q < heights.length - 1 && heights[q + 1] >= heights[i]) {
++q;
}
if ((tmp = (q - p + 1) * heights[i]) > aws) {
aws = tmp;
}
}
return aws;
}
}
单调栈法
暴力解法中对于每个元素,都要确定其左右边界,假如元素是递增关系,那么对于这些元素的右边界的可以一次求出来,即找到右边第一个比当前值小的元素。所以,如果当前元素依次递增,大可不必立马求出其右边界,可以将其放入栈中,然后出栈时,即可确定右边界。
上面讲了如何确定右边界,那么该如何确定左边界,刚开始我误认为左边界就是当前元素,因为元素是递增的。 但是只能确保当前元素到右边界(不包括)这一段内是递增的。对于左边界,无法确定。最后想到可以通过次栈顶+1来找到左边界。
代码实现的总体思路也比较简单,如果当前元素大于等于当前元素,则入栈,否则出栈。但是有些细节比较恼人。
- 栈空时,不能出栈。(虽然这是一句废话)
- 所有元素递增,则最后需要强制出栈。
- 如果栈中仅有一个元素,那么左边界为0。
代码如下:
class Solution {
public int largestRectangleArea(int[] heights) {
int aws = 0, i = 0, tmp;
Deque<Integer> stack = new ArrayDeque<>(heights.length);
while (i <= heights.length) {
if ((i == heights.length ? -1 : heights[i]) < (stack.isEmpty() ? -1 : heights[stack.getLast()])) { // 出栈
if((tmp = heights[stack.pollLast()] * (i - (stack.isEmpty() ? 0 : stack.getLast() + 1))) > aws) {
aws = tmp;
}
} else { // 入栈
stack.addLast(i++);
}
}
return aws;
}
}
暴力解法占用了1188ms,而单调栈法仅用了16ms。
上述代码是针对于依次递增的情况,其实也可以采用依次递减的方案。