求最大子矩阵的大小

题目

给定一个整数map,其中的值只有0和1两种,求其中全是1的所有矩形区域中最大的矩形区域为1的数量。
  例如:
   1 1 1 0
其中,最大矩形区域有3个1,所以返回3。
  例如:
   1 0 1 1
   1 1 1 1
   1 1 1 0
其中,最大的矩形区域有6个1,所以返回6。

要求

如果矩阵为O(NxM),做到时间复杂度为O(NxM)

思路

  1.矩阵的行数为N,以每一行做切割,统计以当前行作为底的情况下,每个位置往上的1的数量。使用高度数组 height米表示。
  例如:
   map = 1 0 1 1
      1 1 1 1
      1 1 1 0
  以第1行做切割后,height={1,0,1,1}, height[j]表示在目前的底(第1行)的 j 位置往上(包括 j 位置),有多少个续的1。
  以第2行做切割后, height={2,1,2,2}, height[j]表示在目前的底(第2行)的 j 位置往上(包括 j 位置),有多少个连续的1。注意到从第一行到第二行,height数组的更新是十分方便的,height[j]=map[i][j]==0 ? 0 : height[j]+1。
  以第3行做切割后, height={3,3,2,0}, height表示在目前的底(第3行)的 j 位置往上(包括j位置),有多少个连续的1。
  2.对于每一次切割,都利用更新后的 height数组来求出以当前行为底的情况下,最大的矩形是什么。那么这么多次切割中,最大的那个矩形就是我们要的答案
  整个过程就是如下代码中的 maxrecsize方法。步骤2的实现是如下代码中的maxrecfrom Bottom方法。
  下面重点介绍一下步操2如何快速地实现,这也是这道题最重要的部分,如果height数组的长度为M,那么求解步2的过程可以做到时问复杂度为O(M)
  对于 height数组,读者可以理解为一个直方图,比如{3,2,3,0},其实就是如下图所示的直方图。

示意图

  也就是说,步骤2的实质是在一个大的直方图中求最大矩形的面积,如果我们能够求出以每一根柱子扩展出去的最大矩形,那么其中最大的矩形就是我们想找的。比如:
  ●   第1根高度为3的柱子向左无法扩展,它的右边是2,比3小,所以向右也无法扩最,则以第1根柱子为高度的矩形面积就是3 * 1==3;
  ●   第2根高度为2的柱子向左可以扩1个距离,因为它的左边是3,比2大;右边的柱子也是3,所以向右也可以扩1个距离,则以第2根柱子为高度的矩形面积就是2 * 3==6;
  ●   第3根高度为3的柱子向左没法扩展,向右也没法扩展。以第3根框子为高度的矩形面积就是3 * 1==3;
  ●   第4根高度为0的柱子向左没法扩晨,向右也没法扩展,则以第4根柱子为高度的短形面积就是0 * 1==0;
  所以,当前直方图中最大的矩形面积就是6,也就是上图中虚线框住的部分。
  考查每一根柱子最大能扩多大,这个行为的实质就是找到柱子左边离它最近且比它小的柱子位置在哪里,以及右边离它最近且比它小的柱子位置在哪里,这个过程怎么计算最快呢?利用单调,如果对单调栈不是特别清楚的读者可以去查看我写的单调栈结构
  为了方便表述,我们以 height={3,4,5,4,3,6}为例说明如何根据 height数组求其中的最大矩形。具体过程如下:
   1. 生成一个栈,记为stack从左到右遍历height数组,每历一个位置,都会把位置压stack中。
   2. 遍历到height的0位置,height[0] = 3,此时stack为空,直接将位置压入栈中,此时stack从栈顶到栈底为{0}。
   3. 通为到height的1位置,height[1]=4,此时stack的栈顶为位置0,值为 height[0]=3,又有height[1] > height[0],那么将位置1直接压入stack。这一步体现了遍历过程中的一个关键逻辑;只有当前 i 位置的值height[i]大于当前栈顶位置所代表的值( height[stack.peek()]),则 i 位置才可以压入 stack。
  所以可以知道,stack中从栈顶到栈底的位置所代表的值是依次递减并且无重复值,此时stack从栈顶到栈底为{1,0}。
   4. 遍历到height的2位置,height[2] = 5,与步骤3的情况完全一样,所以直接将位置2压入stack,此时stick从栈顶到底为{2,1,0}。
   5.遍历到 height的3位置, height[3] = 4,此时stack的栈顶为位置2,值为 height[2]=5,又有 heght[3] < height[2],此时又出了一个遍过程中的关键逻辑,即如果当前 i 位置的值 height[i]小于或等于当前栈顶位置所代表的值(height[stack.peek()]),则把栈中存的位置不断弹出,直到某一个栈顶所代表的值小于height[i],再把 i 位置压入,并在这期间做如下处理:
  1)假设当前弹出的栈顶位置记为位置 j,弹出栈顶之后,新的栈顶记为k。然后开始考虑位置 j 的柱子向右和向左最远能扩到哪里。
  2)对位置 j 的柱子来说,向右最远能扩到哪里呢?
  如果 height[j] == height[i],那么 j -1 位置就是向右能扩到的最远位置。j 之所以被弹出,就是因为遇到了第一个比 j 位置值小的位置。
  如果 height[j]=height[i],那么j -1位置不一定是向右能扩到的最远位置,只是起码能扩到的位置。那怎么办呢?
  可以肯定的是,在这种情况下,i 位置的柱子向左必然也可以扩到 j 位置。也就是说,j 位置的柱子扩出来的最大矩形和 i 位置的柱子扩出来的最大矩形是同一个。
  所以,此时 j 位置的柱子能扩出来的最大矩形虽然无法被正确计算,但不要紧,因为 i 位置肯定要压入到栈中,那么 j 位置和 i 位置共享的最大矩形就等 i位置弹出的时候再计算即可。
  3)对 j 位置的柱子来说,向左最远能扩到哪望呢?
  根据单调的性质,k 位置的值是 j 位置的值左边离 j 位置最近的比 j 位置的值小的位置,所以 j 位置的柱子向左最远可以扩到 k+1 位置。
  4)综上所述,j 位置的柱子能扩出来的最大矩形为(i-k-1) * height[j]。
以例子来说明。
   ① i == 3, height[3] = 4,此时 stack的栈顶为位置2,值为height[2] = 5,故 heights[3]<= height[2],所以位置2被弹出(j == 2),当前栈顶变为1(k == 1)。位置2的柱子扩出来的最大矩形面积为(3-1-1) * 5 == 5。
   ② i == 3, height[3] = 4,此时 stack的项为位置1,值为 height[1]=4。故 height[3]<= height[2],所以位置1被弹出(j == 1),当前核顶变为1(k=0)。位置1的柱子扩出来的最大矩形面积为(3-0-1) * 4==8,这个值实际上是不对的(偏小),但在位置3被弹出的时候是能够重新正确计算得到的。
   ③ i ==3 ,height[3]=4,此时 stack的栈顶为位置0,值为 heigh[0]=3,这时 height[3]<= height[2],所以位置0不弹出。
   ④ 将位置3压入 stack, stack从根顶到核底为{3,0}
  6. 遍历到height的4位置, height[4]=3、与步骤5的情况类似,以下是弹出过程:
  1) i==4, height[4]=3,此时 stack的栈顶为位置3,值为 height[3]=4,故 height[4]<= height[3],所以位置0被弹出(j==0),当前栈顶变为0(k=0)。位置3的柱子扩出来的最大矩形面积为(4-0-1) * 4=12。这个最大面积也是位置1的柱子扩出来的最大矩形面积,在位置1被弹出时,这个矩形其实没有找到,但在位置3这里找到了。
  2) i==4, height[4]=3,此时 stack的栈顶为位置0,值为 height[0]=3,故 height[4]<= heigh[0],所以位置0被弹出(j==0),当前没有了栈顶元素,此时可以认为k==-1。位置0的柱子扩出来的最大矩形面积为(4-(-1)-1) * 3=12,这个值实际上是不对的(偏小),但在位置4被弹出时是能够重新正确计算得到的。
  3)己经为空,所以将位置4压入 stack,此时从顶到底为{4}。
  7. 遍历到height的5位置,height[5]=6,情况和步骤3类似,直接压入位置5,此时从顶到底为{5,4}。
  8. 遍历结束后,stack中仍有位置没有经历扩的过程,从栈顶到栈底为{5,4},此时因为 height数组再往右不能扩出去,所以认为 i = height.length==-6且越界之后的值极小,然后开始弹出留在栈中的位置:
  1) i==6,height[6]极小,此时 stack的顶为位置5,值为 height[5]=6,故 height[6]<=height[5],所以位置5被弹出(j==5),当前栈顶变为4(k=4)。位置5的柱子扩出来的最大矩形面积为(6-4-1) * 6=6
  2) i==6,height[6]极小,此时stack的栈顶为位置4,值为height[4]=3,故 height[6] <= height[4],所以位置4被弹出(j==4),栈空了,此时可以认为k=-1,位置4的柱子扩出来的最大矩形面积为(6-(-1)-1) * 3==18。这个最大面积也是位置0的柱子扩出来的最大矩形面积,在位置0被弹出的时候,这个矩形其实没有找到,但在位置4这里找到了。
  3)栈已经空了,过程结束。
  9. 整个过程结束,所有找到的最大矩形面积中18是最大的,所以返回18
  研究以上9个步骤时我们发现,任何一个位置都仅仅进出1次,所以时向复余度为O(M)。既然每做一次切割处理的时间复杂度O(M),一共做N次,那么总的时间复杂度为O(NxM)。
  全部过程参看如下代码中的 makrecsiie方法,9个步骤的详翻过程参看代码中的maxRecFromBottom方法。

代码演示

package com.itz.zcy.stackAndQueue;

import java.util.Stack;

/**
 * 给定一个整数map,其中的值只有0和1两种,求其中全是1的所有矩形区域中最大的矩形区域为1的数量。
 *  例如:
 *   1 1 1 0
 * 其中,最大矩形区域有3个1,所以返回3。
 *  例如:
 *  1 0 1 1
 *  1 1 1 1
 *  1 1 1 0
 * 其中,最大的矩形区域有6个1,所以返回6。
 */
public class MaxRec {

    /**
     * 每一行切割后对每一行使用单调栈的结构来他们最大能扩大矩形面积
     * @param height
     * @return
     */
    public static int maxRecFromBottom(int [] height){
        if(height == null||height.length ==0){
            return 0;
        }
        int maxArea = 0;
        Stack<Integer> stack = new Stack<>();
        for (int i=0;i<height.length;i++){
            while (!stack.isEmpty()&&height[i]<=height[stack.peek()]){
                int j = stack.pop();
                int k = stack.isEmpty()?-1:stack.peek();
                int curArea = (i-k-1)*height[j];
                maxArea = Math.max(maxArea,curArea);
            }
            stack.push(i);
        }
        while (!stack.isEmpty()){
            int j = stack.pop();
            int k = stack.isEmpty()?-1:stack.peek();
            int curArea = (height.length-k-1)*height[j];
            maxArea = Math.max(maxArea,curArea);
        }
        return maxArea;
    }

    /**
     * 对每一行进行切割
     * @param map
     * @return
     */
    public int maxRecSize(int [][] map ){
        if (map == null||map.length==0||map[0].length==0){
            return 0;
        }
        int maxArea = 0;
        int [] height = new int[map.length];
        for (int i=0;i<map.length;i++){
            for (int j=0;j<map[0].length;j++){
                height[j] = map[i][j] == 0?0:height[j]+1;
            }
            maxArea = Math.max(maxRecFromBottom(height),maxArea);
        }
        return maxArea;
    }
}

总结

究以上9个步骤时我们发现,任何一个位置都仅仅进出1次,所以时向复余度为O(M)。既然每做一次切割处理的时间复杂度O(M)(单调栈),一共做N次,那么总的时间复杂度为O(NxM)。

文献:左程云著 《程序员代码面试指南IT名企算法与数据结构题目最优解》(第二版)
版权声明:此文版权归作者所有!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 题目 给定一个整型矩阵map, 其中的值只有0 和 1 两种, 求其中全是1 的所有矩形区域中, 最大的矩形区域为...
    囧略囧阅读 3,896评论 1 2
  • 这个题考察的是动态规划,并且要了解LeetCode第84题,要清楚直方图的面积怎么计算。 85.最大矩阵(Leet...
    Michaelhbjian阅读 362评论 0 0
  • public class ImageProcessHelper { ///////////////////////...
    学习不断阅读 2,597评论 0 1
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,084评论 1 32
  • 感悟:大的公司组织分为小的个体,把公司内部市场化,个人感觉这个量真的很难把握,尤其是各个部门的公允的服务定价,很难...
    临淄茂业DDM黄红阅读 131评论 0 0