思路
可以用暴力、分治、单调栈解决,我选了单调栈的做法~
- 我们知道在计算某一个柱子围城的最大矩形面积时,目的是要寻找他左右两边第一个比他低的柱子。也就是说,我们在扫描到某一个柱子时要从后向前,挨个通知比他低的柱子,因为当前的柱子就是他前面相邻的所有比他低的柱子的右边界!
- 这种扫描到某个地方先保存,等到某个时机成熟时从后往前通知自然要用栈来实现
- 因此可以用栈来模拟这个过程(为了计算面积,存的是索引)
- 在扫描到某个柱子时,如果它比栈顶元素对应的height大,那就入栈
- 如果他比栈顶元素对应的height小,那就把栈顶元素一直pop,直到他比栈顶元素大,在这个过程中要同时计算矩形面积
- 具体的计算方法为,我们假设栈顶元素为i,当前元素为j,栈顶下方元素为k,此时需要计算的是以栈顶的柱子围成的最大矩形。我们知道当前的索引是栈顶矩形的右边界(因为现在的柱子肯定比栈顶的柱子小),那么根据之前入栈的条件,栈顶下方的元素一定是左边界,具体的计算方法为(j-k)*heights[i]
- 当栈中所有比当前元素对应的height大的索引弹出后,还要把当前元素push进去,因为我们只有在pop的过程中才计算了某个柱子可以围成的最大矩形
- 当柱子遍历结束后,栈中可能还有元素,这是当然是有可能发生的~因为会看前面的过程,只有当前元素比栈顶元素对应的height小的时候才会出栈,那么如果某个柱子比后面所有的柱子都要低,他自然不会出栈,那么此时应该处理这些在栈中的元素。这些元素的右边界是最右边,那么此时的j应该是heights.length,i自然还是栈顶元素
- 到此我们还漏了最至关重要的一步,那就是左侧的边界!我们说某个柱子围成的最大矩形,两侧都应该达到第一个比他低的柱子,假如右侧没有比他低的,那他的右边界就是heights.length,而左侧,自然就是-1,为了保证判断的连续性,我们最开始要把-1加入到栈中,这样在得到左边界时我们依旧可以通过栈顶下方的元素j来获得到~
class Solution {
public int largestRectangleArea(int[] heights) {
int max = Integer.MIN_VALUE;
Stack<Integer> s = new Stack<>();
s.push(-1);
for (int i = 0; i < heights.length; i++) {
while (-1 != s.peek() && heights[i] < heights[s.peek()]) {
int index = s.pop();
max = Math.max(max, (i - s.peek() - 1) * heights[index]);
}
s.push(i);
}
while (-1 != s.peek()) {
int index = s.pop();
max = Math.max(max, (heights.length - s.peek() - 1) * heights[index]);
}
return max;
}
}
Go代码
func largestRectangleArea(heights []int) (result int) {
var stack []int
for i, height := range heights {
for len(stack) != 0 && heights[stack[len(stack)-1]] >= height {
rectangleHeight := heights[stack[len(stack)-1]]
stack = stack[0 : len(stack)-1]
var left int
if len(stack) == 0 {
left = -1
} else {
left = stack[len(stack)-1]
}
result = max(result, rectangleHeight*(i-left-1))
}
stack = append(stack, i)
}
right := len(heights)
for len(stack) != 0 {
height := heights[stack[len(stack)-1]]
stack = stack[0 : len(stack)-1]
var left int
if len(stack) == 0 {
left = -1
} else {
left = stack[len(stack)-1]
}
result = max(result, height*(right-left-1))
}
return
}
func max(m, n int) int {
if m >= n {
return m
}
return n
}