[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 (分糖果)的第二種解法。

題外話:最近在看《一萬小時理論》,感覺到所謂天才,不過是不停的總結,在練習和總結中,慢慢地能夠熟練運用正確的思考方法和找到正確的思路,從而可以在較短時間內給出解的人。一起努力加厚自己的髓鞘質吧 :)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM