LeetCode 84. Largest Rectangle in Histogram 直方图里的最大长方形

原题

Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

LeetCode 84. Largest Rectangle in Histogram 直方图里的最大长方形

Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].

LeetCode 84. Largest Rectangle in Histogram 直方图里的最大长方形

The largest rectangle is shown in the shaded area, which has area = 10 unit.

 

示例

Given heights = [2,1,5,6,2,3],
return 10.

解题思路

第一阶段:

拿到这种题,首先想怎么用最暴力的方法解决这个问题,于是自然而然的想出来枚举的方法,即利用二重循环枚举所有可能的区间,接着再这区间中找出最小的值,然后用(最小值 * 区间宽度)求出每个区间的最大值,最后从这些最大值中找出最大解。

 

这种解法的时间复杂度是O(n^3),一般都会超时的,那么我们就会想着降低时间复杂度,即去冗余的方法(冗余分为重复计算和不需要计算两种)

第二阶段:

那么哪里存在冗余呢?第一个想到的应该是区间内求最小值时存在重复计算,我们可以使用空间换时间的方法将之前算过的最小值存储下来,这样求最小值[i][j] 就等于MIN( 最小值[i][j-1] ,  j) ,这样我们就时间复杂度降低到了O(n^2)

第三阶段:

通过推演又进一步发现了冗余的地方,比方说对于[9, 8, 10, 3, 12, 11,15]来说,虽然我们枚举了所有可能的区间,计算了其区间内的最小值,但你有没有发现,其中大多数的最小值都是 3, 那么我们能不能改变思路减少该冗余呢?

于是乎我们可以采用枚举最小值的算法,即假设每一个点都是最小值,然后我们计算出其作为最小值可以向左右两边延伸的最大区间(如果该点左边的点值大于该点,则继续往左比较,知道某点比该点值小为止)

最后可以算出每一个点作为最小值所包括的最大面积。面积 = n点高度 * (n右边界 - n左边界 + 1 ) 

第四阶段:

上一阶段的时间复杂度似乎并没有减少,那么肯定这种新思路引来了新的冗余,这种冗余在哪里呢?

我们发现在找一个点的左右最大区间时存在重复计算,因为如果n点比n - 1点的值大的话,那么n点左边界应该大于等于n - 1点的左边界,于是乎我们可以存储下每个点的左右边界,避免很多重复计算

最终思路:

  1. 枚举所有点,将其作为最小值
  2. 记录每个点的左右边界(计算n点左边界的方法是:判断n是否比n - 1小,如果成立则跳到n - 1 点的左边界x, 比较n是否比x小,如此循环,知道求出左边界
  3. 枚举每一个点的最大面积,计算最大解

完整代码

public class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        if (n == 0) {
            return 0;
        }
        // 求左边的边界
        int[] left = new int[n];
        left[0] = 0;
        for (int i=1;i<n;++i) {
            int A = i;
            while (A > 0 && heights[A - 1] >= heights[i]) {
                A = left[A - 1];
            }
            left[i] = A;
        }
        // 求右边的边界
        int[] right = new int[n];
        right[n - 1] = n - 1;
        for (int i=n-2;i>=0;--i) {
            int A = i;
            while (A < n - 1 && heights[A + 1] >= heights[i]) {
                A = right[A + 1];
            }
            right[i] = A;
        }
        // 枚举每一个最小值的最大面积
        int ans = 0;
        for (int i=0;i<n;++i) {
           ans = Math.max(ans, heights[i] * (right[i] - left[i] + 1));
        }
        return ans;
    }
}