給定一個僅包含 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;
}
};