Given a 2D binary matrix filled with 0's and 1's, find the largest square containing all 1's and return its area.
For example, given the following matrix:
1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0
Return 4.
Credits:
Special thanks to @Freezen for adding this problem and creating all test cases.
這道題我剛看到的時候,馬上聯想到了之前的一道 Number of Islands,但是仔細一對比,發現又不太一樣,那道題1的形狀不確定,很適合 DFS 的特點,而這道題要找的是正方形,是非常有特點的形狀,所以並不需要用到 DFS,要論相似,我倒認為這道 Maximal Rectangle 更相似一些。這道題的解法不止一種,我們先來看一種 brute force 的方法,這種方法的機理就是就是把數組中每一個點都當成正方形的左頂點來向右下方掃描,來尋找最大正方形。具體的掃描方法是,確定了左頂點后,再往下掃的時候,正方形的豎邊長度就確定了,只需要找到橫邊即可,這時候我們使用直方圖的原理,從其累加值能反映出上面的值是否全為1,之前也有一道關於直方圖的題 Largest Rectangle in Histogram。通過這種方法我們就可以找出最大的正方形,參見代碼如下:
解法一:
class Solution { public: int maximalSquare(vector<vector<char> >& matrix) { int res = 0; for (int i = 0; i < matrix.size(); ++i) { vector<int> v(matrix[i].size(), 0); for (int j = i; j < matrix.size(); ++j) { for (int k = 0; k < matrix[j].size(); ++k) { if (matrix[j][k] == '1') ++v[k]; } res = max(res, getSquareArea(v, j - i + 1)); } } return res; } int getSquareArea(vector<int> &v, int k) { if (v.size() < k) return 0; int count = 0; for (int i = 0; i < v.size(); ++i) { if (v[i] != k) count = 0; else ++count; if (count == k) return k * k; } return 0; } };
下面這個方法用到了建立累計和數組的方法,可以參見之前那篇博客 Range Sum Query 2D - Immutable。原理是建立好了累加和數組后,我們開始遍歷二維數組的每一個位置,對於任意一個位置 (i, j),我們從該位置往 (0,0) 點遍歷所有的正方形,正方形的個數為 min(i,j)+1,由於我們有了累加和矩陣,能快速的求出任意一個區域之和,所以我們能快速得到所有子正方形之和,比較正方形之和跟邊長的平方是否相等,相等說明正方形中的數字均為1,更新 res 結果即可,參見代碼如下:
解法二:
class Solution { public: int maximalSquare(vector<vector<char>>& matrix) { if (matrix.empty() || matrix[0].empty()) return 0; int m = matrix.size(), n = matrix[0].size(), res = 0; vector<vector<int>> sum(m, vector<int>(n, 0)); for (int i = 0; i < matrix.size(); ++i) { for (int j = 0; j < matrix[i].size(); ++j) { int t = matrix[i][j] - '0'; if (i > 0) t += sum[i - 1][j]; if (j > 0) t += sum[i][j - 1]; if (i > 0 && j > 0) t -= sum[i - 1][j - 1]; sum[i][j] = t; int cnt = 1; for (int k = min(i, j); k >= 0; --k) { int d = sum[i][j]; if (i - cnt >= 0) d -= sum[i - cnt][j]; if (j - cnt >= 0) d -= sum[i][j - cnt]; if (i - cnt >= 0 && j - cnt >= 0) d += sum[i - cnt][j - cnt]; if (d == cnt * cnt) res = max(res, d); ++cnt; } } } return res; } };
我們還可以進一步的優化時間復雜度到 O(n2),做法是使用 DP,建立一個二維 dp 數組,其中 dp[i][j] 表示到達 (i, j) 位置所能組成的最大正方形的邊長。我們首先來考慮邊界情況,也就是當i或j為0的情況,那么在首行或者首列中,必定有一個方向長度為1,那么就無法組成長度超過1的正方形,最多能組成長度為1的正方形,條件是當前位置為1。邊界條件處理完了,再來看一般情況的遞推公式怎么辦,對於任意一點 dp[i][j],由於該點是正方形的右下角,所以該點的右邊,下邊,右下邊都不用考慮,關心的就是左邊,上邊,和左上邊。這三個位置的dp值 suppose 都應該算好的,還有就是要知道一點,只有當前 (i, j) 位置為1,dp[i][j] 才有可能大於0,否則 dp[i][j] 一定為0。當 (i, j) 位置為1,此時要看 dp[i-1][j-1], dp[i][j-1],和 dp[i-1][j] 這三個位置,我們找其中最小的值,並加上1,就是 dp[i][j] 的當前值了,這個並不難想,畢竟不能有0存在,所以只能取交集,最后再用 dp[i][j] 的值來更新結果 res 的值即可,參見代碼如下:
解法三:
class Solution { public: int maximalSquare(vector<vector<char>>& matrix) { if (matrix.empty() || matrix[0].empty()) return 0; int m = matrix.size(), n = matrix[0].size(), res = 0; vector<vector<int>> dp(m, vector<int>(n, 0)); for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (i == 0 || j == 0) dp[i][j] = matrix[i][j] - '0'; else if (matrix[i][j] == '1') { dp[i][j] = min(dp[i - 1][j - 1], min(dp[i][j - 1], dp[i - 1][j])) + 1; } res = max(res, dp[i][j]); } } return res * res; } };
下面這種解法進一步的優化了空間復雜度,此時只需要用一個一維數組就可以解決,為了處理邊界情況,padding 了一位,所以 dp 的長度是 m+1,然后還需要一個變量 pre 來記錄上一個層的 dp 值,我們更新的順序是行優先,就是先往下遍歷,用一個臨時變量t保存當前 dp 值,然后看如果當前位置為1,則更新 dp[i] 為 dp[i], dp[i-1], 和 pre 三者之間的最小值,再加上1,來更新結果 res,如果當前位置為0,則重置當前 dp 值為0,因為只有一維數組,每個位置會被重復使用,參見代碼如下:
解法四:
class Solution { public: int maximalSquare(vector<vector<char>>& matrix) { if (matrix.empty() || matrix[0].empty()) return 0; int m = matrix.size(), n = matrix[0].size(), res = 0, pre = 0; vector<int> dp(m + 1, 0); for (int j = 0; j < n; ++j) { for (int i = 1; i <= m; ++i) { int t = dp[i]; if (matrix[i - 1][j] == '1') { dp[i] = min(dp[i], min(dp[i - 1], pre)) + 1; res = max(res, dp[i]); } else { dp[i] = 0; } pre = t; } } return res * res; } };
類似題目:
Largest Rectangle in Histogram
參考資料:
https://leetcode.com/problems/maximal-square/
https://leetcode.com/problems/maximal-square/discuss/61803/c-dynamic-programming
https://leetcode.com/problems/maximal-square/discuss/61913/my-concise-solution-in-c