LeetCode - 84. 柱状图中最大的矩形

304 阅读4分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。


原题:84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

image.png

如上图,最终结果的矩形就是图中的红色矩形,它的面积是 10。

解题思路:

根据上图,最容易想到的办法,就是暴力求解:遍历数组中的每一个元素,然后一这个元素为起点,分别向左和向右眼神,知道遇到比这个元素低的柱子,那么就找到了一个高度为当前元素高度、宽度为左右延伸距离之和加元素本身的1个宽度的矩形。通过对每个元素都进行这样的运算,边计算边比较,就可以得到最大的值。

这个方法相当于对数组进行了两层遍历,时间复杂度为 O(n^2)。我们需要找到优化的空间,以上解法的问题是,在遍历数组的过程中,很多数据没有留存,导致每遍历到一个元素都需要在整个数组寻找当前元素高度可以勾勒的巨型面积的宽度。我们可以通过空间换时间的方式,记录遍历过程中的数据,实现时间复杂度的优化。

首先,还是从头到尾遍历数组,在遍历的过程中确定当前元素高度可以勾勒的矩形的左右边界。

以开头的图为例,当遍历到下标为 0 的柱子,高度为 2,因为它左边没有其他元素,因此它的左边解释确定的,但是因为还不知道后面柱子的高度,因此右边界还无法确认。接着遍历到下标为 1 的柱子,高度是 1,比 2 小,此时,高度是 2 的柱子能勾勒的矩形的右边界就确认了。此时,就不需要在考虑高度是 2 的那个柱子。

但是高度是 1 的柱子的右边界依然不确定,需要接着向后遍历。通过不断重复以上向后遍历和高度比较的过程,我们可以发现,假设当前便利到的柱子下标是 curr,当下标是 curr + 1 的柱子比 curr 更高的时候,我们无法确定 curr 的右边接,但是当它更低的时候,之前的比 curr + 1 更高的所有柱子的右边界就都确认了。

因此,在我们便利柱子高度的过程中,如果 curr + 1 比 curr 更高,我们就记录下来,如果更低,那么就从之前记录的高度中寻找它的左边界,当 curr 的高度所能勾勒出来的矩形被确认并计算出它的面积之后,就从我们记录的数据中清除。

这样我们记录的所有柱子,他们的高度就是递增的。(便利的过程中,如果柱子不断变高就记下来,如果变低,就找出前面更高的柱子,计算他们对应的结果,并清楚),并且,更后记录的柱子,会更早地确认矩形的面积,也就是后进先出。因此,我们记录数据的数据结构是一个单调的栈。

最后,当便利完所有元素,并计算出以他它们的高度能勾勒出的矩形的面积之后,最终得到过程中比较出的最大值。

另外,由于过程中需要对头尾的元素进行判断做不同的逻辑处理,为了简化运算,我们可以在数组头尾各加入一个高度为 0 的柱子,这样可以免去判断的麻烦,且不影响结果。

最终代码:

class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        if (n == 0) {
            return 0;
        }
        if (n == 1) {
            return heights[0];
        }
        int[] newHeights = new int[n + 2];
        System.arraycopy(heights, 0, newHeights, 1, n);
        heights = newHeights;
        n += 2;
        int max = 0;
        Deque<Integer> stack = new LinkedList<>();
        stack.push(0);
        for (int i = 1; i < n; i++) {
            while (heights[stack.peek()] > heights[i]) {
                int h = heights[stack.pop()];
                while (!stack.isEmpty() && heights[stack.peek()] == h) {
                    stack.pop();
                }
                int w = i - stack.peek() - 1;
                max = Math.max(max, w * h);
            }
            stack.push(i);
        }
        return max;
    }
}