【LeetCode】85. 最大矩形 (单调栈经典应用)

给定一个仅包含 0 和 1 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。
示例:
输入:
[
[“1”,“0”,“1”,“0”,“0”],
[“1”,“0”,“1”,“1”,“1”],
[“1”,“1”,“1”,“1”,“1”],
[“1”,“0”,“0”,“1”,“0”]
]
输出: 6
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximal-rectangle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路

根据这个图,我们很容易可以算出,每个1的上面有几个连续的1,例如样例:

  ["1","0","1","0","0"],
  ["1","0","1","1","1"],
  ["1","1","1","1","1"],
  ["1","0","0","1","0"]

我们可以求出一个h[][],用于表示,每个1的作为底,他的高度为多少,此处只需要遍历一遍数组即可求出,此处复杂度为O(NM)
那么这个例子求出来的h[][]就是

-> 1 0 1 0 0 
-> 2 0 2 1 1 
-> 3 1 3 2 2 
-> 4 0 0 3 0 

我们可以把每个数字看作是一个高度为h[i][j]的柱子。
遍历每一行,求出以该行为底,最大矩形面积为多少。
最后在所有行的答案中取一个最大值,即为最终答案。

那么我们只要求出一行中的最大面积即可。


以第三行举例:

3 1 3 2 2

假设我们求出两个数组l[]r[],用于表示第i个柱子最左边可以延长到的位置与最右边可以延长的位置,
也就是
l[i]保存比第i个柱子小的,在i左边的最接近i的柱子的下标。(若左边没有比i小的,则l[i] = -1
r[i]保存比第i个柱子小的,在i右边的最接近i的柱子的下标。(若右边没有比i小的,则r[i] = len
那么第三行对应的l[i]r[i]

下标为 0 到 len - 1 
h[] =  3  1 3 2 2

l[] = -1 -1 1 1 1
r[] =  1  5 3 5 5

当我们求出l[]r[]的时候,我们可以算出对应i当前的高度的矩形面积为(r[i] - l[i] - 1) * h[i]


那么问题就剩下一个:如何求出l[]r[]

  • 不是很简单嘛,对于每个i,往左扫描求出l[i],再往右扫描求出r[i]
    这样的确很简单,但是对于每个数字,都需要左右扫描一遍,求出一行中的所有l[i]r[i],则是O(m * m)。再加上我们是一个矩形,有n行,那么复杂度就是O(n * m * m)
  • 单调栈
    单调栈是一个经典算法。在一个栈内维护一个单调的序列,可以为单调递增,也可以单调递减。
    在这个题目的背景中,我们的目标是,对于每一个i,找到他左边第一个比他小的。相反方向同理。

我们以一个方向举一个例子:如何求出l[]

对于每个i,从右往左看,我们只关心第一个小于当前高度的数字在什么位置,而不在乎更左边的数字是多少,也就是说,当有一个小的数字出现以后,再往左的大数字是没有意义的。
例如

h[] = 3 2 1 5 6

我们站在数字5的位置往左看。找到1以后就可以不必往左找了,再往左的2,3都是没有意义的。因为数字1限定了当前的上限。
那么我们在维护栈的时候,如果要入栈一个较小的数字,那么大于这个数字的所有数都可以出栈了。因为小的数字已经限定了高度的上限。
所以自然而然我们从左到右就维护出了一个递增序列。(还不明白就手算一下上面的h[]是如何计算l[]的)

用单调栈的原理,我们可以一遍扫描就求出l[],复杂度O(m)。再反方向扫一遍求出r[]
对于n行,每一行都这样求一次,总复杂度就为O(n * m)


代码

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        int n = matrix.size();
        if(n == 0) return 0;
        int m = matrix[0].size();
        if(m == 0) return 0;
        int dp[n + 10][m + 10] = {0};
        int h[n + 10][m + 10] = {0};
        for (int j = 0; j < m; j++) {
            for (int i = 0; i < n; i++) {
                if(i == 0){
                    h[i][j] = (matrix[i][j] == '1');
                } else {
                    h[i][j] = matrix[i][j] == '1' ? h[i - 1][j] + 1 : 0; 
                }
            }
        }
        int ans = 0;
        int l[m + 10] = {0};
        int r[m + 10] = {0};

        vector<int> st;
        for (int i = 0; i < n; i++) {
            st.clear();
            for (int j = 0; j < m; j++) {
                while(!st.empty() && h[i][st.back()] >= h[i][j]) {
                    st.pop_back();
                }
                if (st.empty()){
                    l[j] = -1;
                } else {
                    l[j] = st.back();
                }
                st.push_back(j); 
            }
            st.clear();
            for (int j = m - 1; j >= 0; j--) {
                while (!st.empty() && h[i][st.back()] >= h[i][j]) {
                    st.pop_back();
                }
                if (st.empty()) {
                    r[j] = m;
                } else {
                    r[j] = st.back();
                }
                st.push_back(j);
                ans = max(ans, (r[j] - l[j] - 1) * h[i][j]);
            }
        }
        return ans;
    }
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章