问题描述
穷举法(穷举行)
对于该问题,首先想到遍历所有柱子情况。这里我们首先根据行来遍历,即依次遍历长度为1、2、3…n 的柱子,计算他们所能形成的最大长方体:其中长方体的长度就是我们遍历的长度,长方体的高度即这些柱子中最低柱子的高度
public int largestRectangleArea(int[] heights) {
int result = 0, length = heights.length;
// 遍历长方体长的起始下标
for (int left = 0; left < length; left++) {
int maxHeight = Integer.MAX_VALUE;
// 遍历长方体长的结束下标
// 通过这两层遍历就可以遍历出所有情况长方体
for (int right = left; right < length; right++) {
// 计算起始下标left,结束下标right下的最低高度
maxHeight = Math.min(maxHeight, heights[right]);
result = Math.max(result, (right - left + 1) * maxHeight);
}
}
return result;
}
穷举法(遍历列)
除了上述遍历行的形式,还可以通过遍历列的形式完成题目。对于任意列:向左向右找到第一个低于它的列,那么中间这部分柱子高度都大于它,也就可以根据它形成长方体。相比遍历行的形式,遍历列的形式只需要一层循环,效率更高。
public int largestRectangleArea2(int[] heights) {
int result = 0, length = heights.length;
// 遍历所有列的情况
for (int temp = 0; temp < length; temp++) {
int height = heights[temp], left = temp - 1, right = temp + 1;
// 向左找出第一个小于它高度列的下标
while (left >= 0 && heights[left] >= height) {
left--;
}
// 向右找出第一个小于它高度列的下标
while (right < length && heights[right] >= height) {
right++;
}
result = Math.max(result, (right - left - 1) * height);
}
return result;
}
单调栈解法
通过上述方法二的代码,我们可以发现:该问题实际上可以转换为寻找某一列左边、右边第一个小于它的列的问题。此时我们就可以维护一个栈分别向左、向右遍历,计算左右两边第一个小于当前列的下标,最终通过遍历找出最大值。
其中栈我们可以维护一个高度从小到大的单调栈,因为要计算下标的缘故,因此栈中保存对应列的下标。此时在入栈时就会出现以下几种情况:
1、入栈时栈为空:栈为空说明当前列前面不存在比它低的列,也就是说前面所有列都比它高,它的左边界是-1
2、入栈时栈不为空:栈不为空时,比较栈顶元素对应列的高度是否低于当前列,如果低于,正常入栈,栈的左边界即栈顶元素对应列。如果高于,出栈栈顶元素,继续判断
public int largestRectangleArea3(int[] heights) {
int result = 0, length = heights.length;
int[] leftIndex = new int[length];
int[] rightIndex = new int[length];
Stack<Integer> stack = new Stack<Integer>();
// 从前往后遍历所有列,计算左边第一个低于当前列的下标
for (int i = 0; i < length; i++) {
// 栈不为空,并且当前遍历列小于栈顶元素列
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
stack.pop();
}
// 此时栈顶元素对应列一定是第一个低于当前列的
// 如果栈为空,说明前面所有列都比它高,赋值-1
leftIndex[i] = stack.isEmpty() ? -1 : stack.peek();
stack.push(i);
}
// 清空栈
while (!stack.isEmpty()) {
stack.pop();
}
// 从后往前遍历,和上面相反,计算右边第一个低于当前列的下标
for (int i = length - 1; i >= 0; i--) {
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
stack.pop();
}
rightIndex[i] = stack.isEmpty() ? length : stack.peek();
stack.push(i);
}
for (int i = 0; i < length; i++) {
// 遍历所有列可能产生的情况,找出最大情况
result = Math.max(result, (rightIndex[i] - leftIndex[i] - 1) * heights[i]);
}
return result;
}
单调栈解法优化
在上述方法3中,我们分别向左、向右两个方向遍历两次寻找左边第一个小于它的列和右边第一个大于它的列。实际上两次遍历是多余的,只需要一次遍历就可以得到所求:从左向右遍历时,假设第 i 列在遍历第 j 列时出栈,也就是说第 j 列的高度小于第 i 列,并且第 j 列是第一个小于第 i 列的,否则再 i-j 之间,第 i 列早已出栈。
唯一不同的是:从左向右遍历时,我们使用大于等于判断,这样在判断时,假设第 i 列 和 第 i+1 列高度相等,那么计算出第 i 列右边第一个比它低的列是第i+1列,但第 i+1 列的数据不会有问题,最终答案不会有问题,也就是说:局部数据可能有问题,计算会偏少,但是最大值不会有问题,不会对题目有影响。
public int largestRectangleArea4(int[] heights) {
int result = 0, length = heights.length;
int[] leftIndex = new int[length];
int[] rightIndex = new int[length];
Stack<Integer> stack = new Stack<Integer>();
for (int i = 0; i < length; i++) {
while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) {
// 出栈时说明当前列是一个小于它的列
rightIndex[stack.peek()] = i;
stack.pop();
}
leftIndex[i] = stack.isEmpty() ? -1 : stack.peek();
stack.push(i);
}
// 没有出栈的说明右边所有列都比它高
while (!stack.isEmpty()) {
rightIndex[stack.peek()] = length;
stack.pop();
}
for (int i = 0; i < length; i++) {
result = Math.max(result, (rightIndex[i] - leftIndex[i] - 1) * heights[i]);
}
return result;
}