zoukankan      html  css  js  c++  java
  • [LeetCode] Largest Rectangle in Histogram O(n) 解法详析, Maximal Rectangle

    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.

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

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

    For example,
    Given height = [2,1,5,6,2,3],
    return 10.

    思路:如果时间复杂度要求是O(n2)的话,解法比较多也比较好理解。比如可以遍历,对于当前 i 位置上的立柱,计算出以这个i 立柱结尾的最大矩形,然后求出总的最大矩形。

    计算以i 立柱结尾的最大矩形又需要一次遍历,因此时间复杂度是 O(n2)。

    或者可以用另一种方法:最大矩形的高度毫无疑问必然和某一个立柱的高度相等,或者说,最大矩形必然包含了某一个立柱的全部。

    因此,可以遍历所有立柱,对当前立柱 i,以其高度左右扩展,看看以当前立柱 i 的高度最多能包含进多大的矩形面积。最后选出最大的总面积即可。这种思路的代码如下:

    class Solution {
    public:
        int largestRectangleArea(vector<int> &height) {
            if(height.size() == 0) return 0;
            int max = 0;
            for(int i = 0; i < height.size(); ++i){
                int mid = i;
                int area = 0;
                for(;mid >= 0 && height[mid] >= height[i]; area += height[i], --mid);
                for(mid = i+1 ;mid < height.size() && height[mid] >= height[i]; area += height[i], ++mid);
                if(max < area) max = area;
            }
            return max;
        }
    };

    一点也不意外,过不了大集合测试。

    但之所以把这个思路介绍一下,是因为这个思路可以孵化出时间复杂度为O(n)的解。

    这种解法委实巧妙,不是我的原创。

    首先我们看一下下面的例子:

    height的内容是 [5,6,7,8,3],特点是除了最后一个,前面全部保持递增,且最后一个立柱的高度小于前面所有立柱高度。

    对于这种特点的柱状图,如果使用上面所说的“挨个使用每一个柱状图的高度作为矩形的高度,求面积”的方法,还需要用嵌套循环吗?

    我们知道除了最后一个,从第一个到倒数第二个立柱的高度都在升高,那么如果挨个使用每一个柱的高度作为矩形的高度,那么依次能得到的矩形的宽度就可以直接算出来:使用5作为高度可以使用前四个立柱组成 4*5的矩形,高度6可以组成3*6的矩形... 因此只需要遍历一次,选出最大面积即可。

    对于这种类型的柱状图,最大矩形面积的时间复杂度是O(n)。

    我们将这种特点的柱状图称为“波峰图”。

    下面介绍新的解法的步骤:

    (1) 在height尾部添加一个0,也就是一个高度为0的立柱。作用是在最后也能凑成上面提的那种“波峰图”。

    (2) 定义了一个stack,然后遍历时如果height[i] 大于stack.top(),进栈。反之,出栈直到栈顶元素小于height[i]。

    由于出栈的这些元素高度都是递增的,我们可以求出这些立柱中所围成的最大矩形。更妙的是,由于这些被弹出的立柱处于“波峰”之上(比如弹出i 到 i+k,那么所有这些立柱的高度都高于 i-1和 i+k+1的高度),因此,如果我们使用之前所提的“左右延伸找立柱”的思路解,以这些立柱的高度作为整个矩形的高度时,左右延伸出的矩形所包含的立柱不会超出这段“波峰”,因为波峰外的立柱高度都比他们低。“波峰图”其实就是求解最大矩形的“孤岛”,它不会干扰到外部。

    (3) 由于比height[i]大的元素都出完了,height[i]又比栈顶元素大了,因此再次进栈。如此往复,直到遍历到最后那个高度为0的柱,触发最后的弹出以及最后一次面积的计算,此后stack为空。

    (4) 返回面积最大值。

    栈中存的不是高度,而是height的索引,这样做的好处是不会影响宽度的计算,索引值相减 = 宽度。

    自己实现代码如下,虽然是二重循环,但时间复杂度实际  2N,故为O(N)

    class Solution {
    public:
        int largestRectangleArea(vector<int> &height) {
            if(height.size() == 0) return 0; 
            stack<int> st;
            int MAX = 0;
            height.push_back(0);
            int leftarea = 0, rightarea = 0;
            for(int i = 0; i < height.size(); ++i){
                while(!st.empty() && height[st.top()] > height[i]){
                    int tmp = st.top();
                    st.pop();
                    leftarea = (st.empty() ? tmp + 1 : tmp - st.top()) * height[tmp]; //以tmp为高度,tmp所在柱以及向左延伸出来的矩形面积
                    rightarea = (i - tmp - 1) * height[tmp]; //以tmp为高度,向右边延伸出来的矩形面积
                    if((leftarea + rightarea) > MAX) MAX = (leftarea + rightarea);
                }
                st.push(i);
            }
            return MAX;
        }
    };

     100 ms AC

    另一版稍简介的代码 ,引自 水中的鱼-[LeetCode] Largest Rectangle in Histogram 解题报告

    1:  int largestRectangleArea(vector<int> &h) {  
    2:       stack<int> S;  
    3:       h.push_back(0);  
    4:       int sum = 0;  
    5:       for (int i = 0; i < h.size(); i++) {  
    6:            if (S.empty() || h[i] > h[S.top()]) S.push(i);  
    7:            else {  
    8:                 int tmp = S.top();  
    9:                 S.pop();  
    10:                 sum = max(sum, h[tmp]*(S.empty()? i : i-S.top()-1));  
    11:                 i--;  
    12:            }  
    13:       }  
    14:       return sum;  
    15:  }  

    108 ms AC 

    此解法最大亮点就在于

    (1) stack里存的是index,计算面积时的宽度使用 index的差值,所以虽然stack 弹出了立柱,但是不影响宽度的计算,依然可以计算面积。

    (2) 这种解法本质上是查看以每一个立柱为矩形高度,求出最大面积,但是它通过入栈出栈,把整个height变成一组组“波峰图”来解,这种高度布局下,最大面积的计算是O(n)的,然后将所有波峰图的最大面积取最大值。最后做到了以O(n)的时间复杂度覆盖了所有的立柱。

    多么精彩的解法!

    接下来还有道Maximal Rectangle 的题,这道题的实用价值很大:算01 矩阵中包含最多1 的矩形。

    Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area.

    有了上一题的基础,这道题就可等效为上一题,对于矩阵每一行,我们将其看作直方图,立柱的高度就是行中元素往上数包含的连续1的个数。

    因此每一行都可以利用上一题方法计算最大矩形,最后求出各行结果的最大值就好了。时间复杂度 O(n2)

    class Solution {
    public:
        int maximalRectangle(vector<vector<char> > &matrix) {
            if(matrix.size() == 0 || matrix[0].size() == 0) return 0;
            int H = matrix.size(), W = matrix[0].size();
            int height[W+1];
            int i, j , MAX = 0, leftarea = 0, rightarea = 0;
            stack<int> st;
            for(i = 0; i <= W; height[i] = 0, ++i);
            for(i = 0; i < H; ++i){
                while(!st.empty()) st.pop();
                for(j = 0; j < W; ++j){
                    if(matrix[i][j] == '1') height[j]++;
                    else height[j] = 0;
                }
                for(int j = 0; j <= W; ++j){
                    while(!st.empty() && height[st.top()] > height[j]){
                        int tmp = st.top();
                        st.pop();
                        leftarea = (st.empty() ? tmp + 1 : tmp - st.top()) * height[tmp];
                        rightarea = (j - tmp - 1) * height[tmp];
                        if((leftarea + rightarea) > MAX) MAX = (leftarea + rightarea);
                    }
                    st.push(j);
                }
            }
            return MAX;
        }
    };

    88ms AC

    总结:

    第一题中,能完成那样精彩的解法,stack 的灵活使用功不可没,这样使用stack可能一上来不容易想到。

    但是如果我们遇到这道题的时候,一开始应该想想特例,比如递增序列下的最大矩形面积,然后发散开来,想想一般情况和这种递增情况的关系,也许就能有突破。使用类似的"从特例到一般"的发散方式还有Candy (分糖果)的第二种解法。

    题外话:最近在看《一万小时理论》,感觉到所谓天才,不过是不停的总结,在练习和总结中,慢慢地能够熟练运用正确的思考方法和找到正确的思路,从而可以在较短时间内给出解的人。一起努力加厚自己的髓鞘质吧 :)

  • 相关阅读:
    字符串 CSV解析 表格 逗号分隔值 通讯录 电话簿 MD
    Context Application 使用总结 MD
    RxJava RxPermissions 动态权限 简介 原理 案例 MD
    Luban 鲁班 图片压缩 MD
    FileProvider N 7.0 升级 安装APK 选择文件 拍照 临时权限 MD
    组件化 得到 DDComponent JIMU 模块 插件 MD
    gradlew 命令行 build 调试 构建错误 Manifest merger failed MD
    protobuf Protocol Buffers 简介 案例 MD
    ORM数据库框架 SQLite 常用数据库框架比较 MD
    [工具配置]requirejs 多页面,多入口js文件打包总结
  • 原文地址:https://www.cnblogs.com/felixfang/p/3676193.html
Copyright © 2011-2022 走看看