柱状图中最大矩形面积

653 阅读1分钟

参考: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;
    }