求最大子矩陣的大小

題目

給定一個整數(shù)map酌住,其中的值只有0和1兩種叼旋,求其中全是1的所有矩形區(qū)域中最大的矩形區(qū)域?yàn)?的數(shù)量伴挚。
??例如:
?? 1 1 1 0
其中靶衍,最大矩形區(qū)域有3個1,所以返回3茎芋。
??例如:
?? 1 0 1 1
?? 1 1 1 1
?? 1 1 1 0
其中颅眶,最大的矩形區(qū)域有6個1,所以返回6田弥。

要求

如果矩陣為O(NxM),做到時間復(fù)雜度為O(NxM)

思路

??1.矩陣的行數(shù)為N涛酗,以每一行做切割,統(tǒng)計(jì)以當(dāng)前行作為底的情況下偷厦,每個位置往上的1的數(shù)量商叹。使用高度數(shù)組 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 位置),有多少個續(xù)的1请唱。
??以第2行做切割后弥咪, height={2,1,2,2}, height[j]表示在目前的底(第2行)的 j 位置往上(包括 j 位置)籍滴,有多少個連續(xù)的1酪夷。注意到從第一行到第二行,height數(shù)組的更新是十分方便的孽惰,height[j]=map[i][j]==0 ? 0 : height[j]+1晚岭。
??以第3行做切割后, height={3,3,2,0}勋功, height表示在目前的底(第3行)的 j 位置往上(包括j位置)坦报,有多少個連續(xù)的1库说。
??2.對于每一次切割,都利用更新后的 height數(shù)組來求出以當(dāng)前行為底的情況下片择,最大的矩形是什么潜的。那么這么多次切割中,最大的那個矩形就是我們要的答案
??整個過程就是如下代碼中的 maxrecsize方法字管。步驟2的實(shí)現(xiàn)是如下代碼中的maxrecfrom Bottom方法啰挪。
??下面重點(diǎn)介紹一下步操2如何快速地實(shí)現(xiàn),這也是這道題最重要的部分嘲叔,如果height數(shù)組的長度為M亡呵,那么求解步2的過程可以做到時問復(fù)雜度為O(M)
??對于 height數(shù)組,讀者可以理解為一個直方圖硫戈,比如{3,2,3,0}锰什,其實(shí)就是如下圖所示的直方圖。

示意圖

??也就是說丁逝,步驟2的實(shí)質(zhì)是在一個大的直方圖中求最大矩形的面積汁胆,如果我們能夠求出以每一根柱子擴(kuò)展出去的最大矩形,那么其中最大的矩形就是我們想找的霜幼。比如:
??● ? 第1根高度為3的柱子向左無法擴(kuò)展嫩码,它的右邊是2,比3小罪既,所以向右也無法擴(kuò)最谢谦,則以第1根柱子為高度的矩形面積就是3 * 1==3;
??● ? 第2根高度為2的柱子向左可以擴(kuò)1個距離萝衩,因?yàn)樗淖筮吺?回挽,比2大;右邊的柱子也是3,所以向右也可以擴(kuò)1個距離猩谊,則以第2根柱子為高度的矩形面積就是2 * 3==6;
??● ? 第3根高度為3的柱子向左沒法擴(kuò)展千劈,向右也沒法擴(kuò)展。以第3根框子為高度的矩形面積就是3 * 1==3牌捷;
??● ? 第4根高度為0的柱子向左沒法擴(kuò)晨墙牌,向右也沒法擴(kuò)展,則以第4根柱子為高度的短形面積就是0 * 1==0暗甥;
??所以喜滨,當(dāng)前直方圖中最大的矩形面積就是6,也就是上圖中虛線框住的部分撤防。
??考查每一根柱子最大能擴(kuò)多大虽风,這個行為的實(shí)質(zhì)就是找到柱子左邊離它最近且比它小的柱子位置在哪里,以及右邊離它最近且比它小的柱子位置在哪里,這個過程怎么計(jì)算最快呢?利用單調(diào)辜膝,如果對單調(diào)棧不是特別清楚的讀者可以去查看我寫的單調(diào)棧結(jié)構(gòu)
??為了方便表述无牵,我們以 height={3,4,5,4,3,6}為例說明如何根據(jù) height數(shù)組求其中的最大矩形。具體過程如下:
?? 1. 生成一個棧厂抖,記為stack從左到右遍歷height數(shù)組茎毁,每歷一個位置,都會把位置壓stack中忱辅。
?? 2. 遍歷到height的0位置七蜘,height[0] = 3,此時stack為空,直接將位置壓入棧中墙懂,此時stack從棧頂?shù)綏5诪閧0}崔梗。
?? 3. 通為到height的1位置,height[1]=4垒在,此時stack的棧頂為位置0,值為 height[0]=3扔亥,又有height[1] > height[0]场躯,那么將位置1直接壓入stack。這一步體現(xiàn)了遍歷過程中的一個關(guān)鍵邏輯旅挤;只有當(dāng)前 i 位置的值height[i]大于當(dāng)前棧頂位置所代表的值( height[stack.peek()])踢关,則 i 位置才可以壓入 stack。
??所以可以知道粘茄,stack中從棧頂?shù)綏5椎奈恢盟淼闹凳且来芜f減并且無重復(fù)值签舞,此時stack從棧頂?shù)綏5诪閧1,0}。
?? 4. 遍歷到height的2位置柒瓣,height[2] = 5儒搭,與步驟3的情況完全一樣,所以直接將位置2壓入stack芙贫,此時stick從棧頂?shù)降诪閧2,1,0}搂鲫。
?? 5.遍歷到 height的3位置褒傅, height[3] = 4辉阶,此時stack的棧頂為位置2,值為 height[2]=5磁滚,又有 heght[3] < height[2]拣挪,此時又出了一個遍過程中的關(guān)鍵邏輯擦酌,即如果當(dāng)前 i 位置的值 height[i]小于或等于當(dāng)前棧頂位置所代表的值(height[stack.peek()]),則把棧中存的位置不斷彈出菠劝,直到某一個棧頂所代表的值小于height[i]赊舶,再把 i 位置壓入,并在這期間做如下處理:
??1)假設(shè)當(dāng)前彈出的棧頂位置記為位置 j,彈出棧頂之后锯岖,新的棧頂記為k介袜。然后開始考慮位置 j 的柱子向右和向左最遠(yuǎn)能擴(kuò)到哪里。
??2)對位置 j 的柱子來說出吹,向右最遠(yuǎn)能擴(kuò)到哪里呢?
??如果 height[j] == height[i]遇伞,那么 j -1 位置就是向右能擴(kuò)到的最遠(yuǎn)位置。j 之所以被彈出捶牢,就是因?yàn)橛龅搅说谝粋€比 j 位置值小的位置鸠珠。
??如果 height[j]=height[i],那么j -1位置不一定是向右能擴(kuò)到的最遠(yuǎn)位置秋麸,只是起碼能擴(kuò)到的位置渐排。那怎么辦呢?
??可以肯定的是,在這種情況下灸蟆,i 位置的柱子向左必然也可以擴(kuò)到 j 位置驯耻。也就是說,j 位置的柱子擴(kuò)出來的最大矩形和 i 位置的柱子擴(kuò)出來的最大矩形是同一個炒考。
??所以可缚,此時 j 位置的柱子能擴(kuò)出來的最大矩形雖然無法被正確計(jì)算,但不要緊斋枢,因?yàn)?i 位置肯定要壓入到棧中帘靡,那么 j 位置和 i 位置共享的最大矩形就等 i位置彈出的時候再計(jì)算即可。
??3)對 j 位置的柱子來說瓤帚,向左最遠(yuǎn)能擴(kuò)到哪望呢?
??根據(jù)單調(diào)的性質(zhì)描姚,k 位置的值是 j 位置的值左邊離 j 位置最近的比 j 位置的值小的位置,所以 j 位置的柱子向左最遠(yuǎn)可以擴(kuò)到 k+1 位置戈次。
??4)綜上所述轩勘,j 位置的柱子能擴(kuò)出來的最大矩形為(i-k-1) * height[j]。
以例子來說明怯邪。
?? ① i == 3赃阀, height[3] = 4,此時 stack的棧頂為位置2擎颖,值為height[2] = 5榛斯,故 heights[3]<= height[2],所以位置2被彈出(j == 2)搂捧,當(dāng)前棧頂變?yōu)?(k == 1)驮俗。位置2的柱子擴(kuò)出來的最大矩形面積為(3-1-1) * 5 == 5。
?? ② i == 3, height[3] = 4允跑,此時 stack的項(xiàng)為位置1王凑,值為 height[1]=4搪柑。故 height[3]<= height[2],所以位置1被彈出(j == 1),當(dāng)前核頂變?yōu)?(k=0)索烹。位置1的柱子擴(kuò)出來的最大矩形面積為(3-0-1) * 4==8工碾,這個值實(shí)際上是不對的(偏小),但在位置3被彈出的時候是能夠重新正確計(jì)算得到的百姓。
?? ③ i ==3 渊额,height[3]=4,此時 stack的棧頂為位置0垒拢,值為 heigh[0]=3旬迹,這時 height[3]<= height[2],所以位置0不彈出求类。
?? ④ 將位置3壓入 stack奔垦, stack從根頂?shù)胶说诪閧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),當(dāng)前棧頂變?yōu)?(k=0)兆衅。位置3的柱子擴(kuò)出來的最大矩形面積為(4-0-1) * 4=12地沮。這個最大面積也是位置1的柱子擴(kuò)出來的最大矩形面積,在位置1被彈出時羡亩,這個矩形其實(shí)沒有找到摩疑,但在位置3這里找到了。
??2) i==4畏铆, height[4]=3雷袋,此時 stack的棧頂為位置0,值為 height[0]=3辞居,故 height[4]<= heigh[0]楷怒,所以位置0被彈出(j==0),當(dāng)前沒有了棧頂元素瓦灶,此時可以認(rèn)為k==-1鸠删。位置0的柱子擴(kuò)出來的最大矩形面積為(4-(-1)-1) * 3=12,這個值實(shí)際上是不對的(偏小)贼陶,但在位置4被彈出時是能夠重新正確計(jì)算得到的刃泡。
??3)己經(jīng)為空巧娱,所以將位置4壓入 stack,此時從頂?shù)降诪椋?}烘贴。
??7. 遍歷到height的5位置禁添,height[5]=6,情況和步驟3類似桨踪,直接壓入位置5老翘,此時從頂?shù)降诪閧5,4}。
??8. 遍歷結(jié)束后馒闷,stack中仍有位置沒有經(jīng)歷擴(kuò)的過程酪捡,從棧頂?shù)綏5诪閧5,4},此時因?yàn)?height數(shù)組再往右不能擴(kuò)出去纳账,所以認(rèn)為 i = height.length==-6且越界之后的值極小逛薇,然后開始彈出留在棧中的位置:
??1) i==6,height[6]極小疏虫,此時 stack的頂為位置5永罚,值為 height[5]=6,故 height[6]<=height[5]卧秘,所以位置5被彈出(j==5)呢袱,當(dāng)前棧頂變?yōu)?(k=4)。位置5的柱子擴(kuò)出來的最大矩形面積為(6-4-1) * 6=6
??2) i==6翅敌,height[6]極小羞福,此時stack的棧頂為位置4,值為height[4]=3蚯涮,故 height[6] <= height[4]治专,所以位置4被彈出(j==4),椩舛ィ空了张峰,此時可以認(rèn)為k=-1,位置4的柱子擴(kuò)出來的最大矩形面積為(6-(-1)-1) * 3==18棒旗。這個最大面積也是位置0的柱子擴(kuò)出來的最大矩形面積喘批,在位置0被彈出的時候,這個矩形其實(shí)沒有找到铣揉,但在位置4這里找到了饶深。
??3)棧已經(jīng)空了,過程結(jié)束逛拱。
??9. 整個過程結(jié)束粥喜,所有找到的最大矩形面積中18是最大的,所以返回18
??研究以上9個步驟時我們發(fā)現(xiàn)橘券,任何一個位置都僅僅進(jìn)出1次额湘,所以時向復(fù)余度為O(M)卿吐。既然每做一次切割處理的時間復(fù)雜度O(M),一共做N次锋华,那么總的時間復(fù)雜度為O(NxM)嗡官。
??全部過程參看如下代碼中的 makrecsiie方法,9個步驟的詳翻過程參看代碼中的maxRecFromBottom方法毯焕。

代碼演示

package com.itz.zcy.stackAndQueue;

import java.util.Stack;

/**
 * 給定一個整數(shù)map衍腥,其中的值只有0和1兩種,求其中全是1的所有矩形區(qū)域中最大的矩形區(qū)域?yàn)?的數(shù)量纳猫。
 *  例如:
 *   1 1 1 0
 * 其中婆咸,最大矩形區(qū)域有3個1,所以返回3芜辕。
 *  例如:
 *  1 0 1 1
 *  1 1 1 1
 *  1 1 1 0
 * 其中尚骄,最大的矩形區(qū)域有6個1,所以返回6侵续。
 */
public class MaxRec {

    /**
     * 每一行切割后對每一行使用單調(diào)棧的結(jié)構(gòu)來他們最大能擴(kuò)大矩形面積
     * @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;
    }

    /**
     * 對每一行進(jìn)行切割
     * @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;
    }
}

總結(jié)

究以上9個步驟時我們發(fā)現(xiàn)倔丈,任何一個位置都僅僅進(jìn)出1次,所以時向復(fù)余度為O(M)状蜗。既然每做一次切割處理的時間復(fù)雜度O(M)(單調(diào)棧)需五,一共做N次,那么總的時間復(fù)雜度為O(NxM)轧坎。

文獻(xiàn):左程云著 《程序員代碼面試指南IT名企算法與數(shù)據(jù)結(jié)構(gòu)題目最優(yōu)解》(第二版)
版權(quán)聲明:此文版權(quán)歸作者所有宏邮!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缸血,隨后出現(xiàn)的幾起案子蜜氨,更是在濱河造成了極大的恐慌,老刑警劉巖属百,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件记劝,死亡現(xiàn)場離奇詭異变姨,居然都是意外死亡族扰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門定欧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渔呵,“玉大人,你說我怎么就攤上這事砍鸠±┣猓” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵爷辱,是天一觀的道長录豺。 經(jīng)常有香客問我朦肘,道長,這世上最難降的妖魔是什么双饥? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任媒抠,我火速辦了婚禮,結(jié)果婚禮上咏花,老公的妹妹穿的比我還像新娘趴生。我一直安慰自己,他們只是感情好昏翰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布苍匆。 她就那樣靜靜地躺著,像睡著了一般棚菊。 火紅的嫁衣襯著肌膚如雪浸踩。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天窍株,我揣著相機(jī)與錄音民轴,去河邊找鬼。 笑死球订,一個胖子當(dāng)著我的面吹牛后裸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冒滩,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼微驶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了开睡?” 一聲冷哼從身側(cè)響起因苹,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎篇恒,沒想到半個月后扶檐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胁艰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年款筑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腾么。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡奈梳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出解虱,到底是詐尸還是另有隱情攘须,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布殴泰,位于F島的核電站于宙,受9級特大地震影響浮驳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜捞魁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一抹恳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧署驻,春花似錦奋献、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至宣吱,卻和暖如春窃这,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背征候。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工杭攻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人疤坝。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓兆解,卻偏偏與公主長得像,于是被迫代替她去往敵國和親跑揉。 傳聞我的和親對象是個殘疾皇子锅睛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 題目 給定一個整型矩陣map, 其中的值只有0 和 1 兩種历谍, 求其中全是1 的所有矩形區(qū)域中现拒, 最大的矩形區(qū)域?yàn)?..
    囧略囧閱讀 3,972評論 1 2
  • 這個題考察的是動態(tài)規(guī)劃,并且要了解LeetCode第84題望侈,要清楚直方圖的面積怎么計(jì)算印蔬。 85.最大矩陣(Leet...
    Michaelhbjian閱讀 365評論 0 0
  • public class ImageProcessHelper { ///////////////////////...
    學(xué)習(xí)不斷閱讀 2,619評論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,103評論 1 32
  • 感悟:大的公司組織分為小的個體,把公司內(nèi)部市場化脱衙,個人感覺這個量真的很難把握侥猬,尤其是各個部門的公允的服務(wù)定價,很難...
    臨淄茂業(yè)DDM黃紅閱讀 137評論 0 0