参考:leetcode-cn.com/problems/la…
给定n个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
下图是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
图中阴影部分为所能勾勒出的最大矩形面积,其面积为10。
一 暴力求解
1 首先能想到的就是暴力求解,即枚举每种柱子相邻的组合,然后将柱子最小高度与宽度相乘得到面积,最大值则是我们想要的结果。
public static int largestRectangleArea(int[] heights) {
int area = 0;
for (int left = 0; left < heights.length; left++) {
int minHeight = Integer.MAX_VALUE;
for (int right = left; right < heights.length; right++) {
minHeight = Math.min(minHeight, heights[right]);
area = Math.max(area, minHeight * (right - left + 1));
}
}
return area;
}
该方法是由一个柱子为起点,从左往右扫描。
2 还有一种暴力求解,是由一个柱子为起点,分别往左右两个方向扫描。当前柱子作为矩形高度,计算最大矩形面积。
public static int largestRectangleArea(int[] heights) {
int area = 0;
for(int i = 0; i < heights.length; i++) {
int width = 1;
int height = heights[i];
for(int j = i - 1; j >= 0; j--) {
if (heights[j] < height) {
break;
}
width ++;
}
for (int j = i + 1; j < heights.length; j++) {
if (heights[j] < height) {
break;
}
width ++;
}
area = Math.max(area, height * width);
}
return area;
}
二 利用Stack维护一个高度递增的柱子index
从上面的暴力解法2中可以看到,向左右寻找高度小于当前柱子的柱子,即可确定矩形的宽度。那么用一个Stack维护一个高度递增的柱子的index看上去似乎是可行的。我们先用图表的方式理一下思路:
1 初始状态
2 将index0压入栈
3 当index为1时,heights[1]比栈顶的height[0]小,则0出栈。此时最大面积为height[0] * 1 = 2
4 index由1到3,柱子高度不断递增,则index1、2、3分别压入栈
5 index来到了4,height[4]小于栈顶的height[3],所以3出栈。此时index3最大矩阵面积为(4 - 1 - 2) * 6 = 6
6 继续将index4的高度height[4]比较栈顶的height[2],栈顶的height[2]大,则2出栈,并且index2的最大矩阵面积为(4 - 1 - 1) * 5 = 10。
7 步骤6之后,由于栈顶的height[1]小于height[4],所以将index4压栈,循环到index5后同理也压入栈。
8 在index5后加上一个虚拟的index6,高度为0。由于栈顶的height[5]大于height[6],所以5出栈。index5的最大矩阵面积为(6 - 1 - 4) * 3 = 3
9 此时栈顶的height[4]大于height[6],所以4出栈。index4最大矩阵面积为(6 - 1 - 1) * 2 = 8
10 此时栈顶的height[1]大于height[6],所以1出栈。index1最大矩阵面积为(6 - 0 - 0) * 1 = 5
综上计算,最大矩形面积为10。代码如下:
public static int largestRectangleArea(int... heights) {
if (heights == null || heights.length == 0) {
return 0;
}
Stack<Integer> stack = new Stack<Integer>();
int area = 0;
for (int i = 0; i <= heights.length; i++) {
int nowRectangle = -1;
if (i < heights.length) {
nowRectangle = heights[i];
}
while (!stack.isEmpty() && nowRectangle <= heights[stack.peek()]) {
int thisHeight = heights[stack.pop()];
int thisWidth = i;
if (!stack.isEmpty()) {
thisWidth = i - stack.peek() - 1;
}
area = Math.max(area, thisHeight * thisWidth);
}
stack.push(i);
}
return area;
}
三 优化暴力解法2,提前计算每个柱子的宽度
通过上述的几种解法,不难看出每个柱子的最大矩形面积公式为
area = (right - left - 1) * height[index]
直接上代码:
public static int largestRectangleArea(int[] heights) {
if (heights == null || heights.length == 0) {
return 0;
}
// 存放左边比它小的下标
int[] leftLess = new int[heights.length];
// 存放右边比它小的下标
int[] rightLess = new int[heights.length];
rightLess[heights.length - 1] = heights.length;
leftLess[0] = -1;
//计算每个柱子左边比它小的柱子的下标
for (int i = 1; i < heights.length; i++) {
int p = i - 1;
while (p >= 0 && heights[p] >= heights[i]) {
// 找到index i左边比它小的index
// p--;
// 代码走到这儿肯定height[leftLess[p] + 1]要比height[i]大,所以p没必要一点一点减1,直接用leftLess[p]来比较
p = leftLess[p];
}
leftLess[i] = p;
}
// 计算每个柱子右边比它小的柱子的下标
for (int i = heights.length - 2; i >= 0; i--) {
int p = i + 1;
while (p < heights.length && heights[p] >= heights[i]) {
// p++ 优化思想同往左边扫描的过程
p = rightLess[p];
}
rightLess[i] = p;
}
int area = 0;
// 以每个柱子的高度为矩形的高,计算矩形的面积。
for (int i = 0; i < heights.length; i++) {
area = Math.max(area, heights[i] * (rightLess[i] - leftLess[i] - 1));
}
return area;
}