Given a m x n
matrix mat
and an integer threshold
, return the maximum side-length of a square with a sum less than or equal to threshold
or return 0
if there is no such square.
Example 1:
Input: mat = [[1,1,3,2,4,3,2],[1,1,3,2,4,3,2],[1,1,3,2,4,3,2]], threshold = 4
Output: 2
Explanation: The maximum side length of square with sum less than 4 is 2 as shown.
Example 2:
Input: mat = [[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2],[2,2,2,2,2]], threshold = 1
Output: 0
Constraints:
m == mat.length
n == mat[i].length
1 <= m, n <= 300
0 <= mat[i][j] <= 104
0 <= threshold <= 105
這道題給了一個 m by n 的二維數組和一個整型數 threshold,讓返回最大正方形的邊長,使得正方形區間內的數字之和小於等於 threshold。像這種求二維數組子區間和的問題,可以很容易聯想到一維數組的求子數組之和的問題,通過建立累加和數組可以快速的求出一維數組中任意區間的子數組之和。對於二維數組也是一樣的道理,可以建立二維的累加和數組,然后遍歷每一個正方形區間,通過累加和數組快速得到其數字之和,然后比較若小於等於 threshold,則用其邊長來更新結果 res 即可。二維累加和數組的大小要比原數組大1,這樣方便處理越界的問題,累加的方法就是當前位置對應的原數組的數字,加上累加數組上方和左邊的數字,減去左上方的數字。
構建完成了累加和數組之后,就可以遍歷所有的正方形區間了。由於只需要一個頂點和邊長就可以唯一的確定一個正方形區間,所以可以遍歷數組中的每一個位置,當作正方形區間的左上頂點,然后遍歷所有不越界的邊長,並快速求區間和。注意求區間和的方法和求累加和數組的方法是有一些區別的,當正方形區間的左上頂點為 (i, j),邊長為 k+1 的時候,則右下頂點為 (i+k, j+k),區間和的計算方法是 sums[i + k][j + k] - sums[i - 1][j + k] - sums[i + k][j - 1] + sums[i - 1][j - 1]
,可以自行比較下和計算累加和數組的區別,然后就是和 threshold 比較了,若小於等於 threshold,則用 k+1 來更新結果 res 即可,參見代碼如下:
解法一:
class Solution {
public:
int maxSideLength(vector<vector<int>>& mat, int threshold) {
int res = 0, m = mat.size(), n = mat[0].size();
vector<vector<int>> sums(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
sums[i][j] = mat[i - 1][j - 1] + sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
for (int k = 0; (i + k) <= m && (j + k) <= n; ++k) {
int total = sums[i + k][j + k] - sums[i - 1][j + k] - sums[i + k][j - 1] + sums[i - 1][j - 1];
if (total <= threshold) res = max(res, k + 1);
}
}
}
return res;
}
};
由於所求的最大邊長是有范圍的,最小為0,最大不超過m和n中的較小值,那么就可以用二分搜索法來增加查找的速度。還是需要建立累加和數組 sums,然后就可以開始二分搜索了,這里用到的二分法是博主之前的總結帖 LeetCode Binary Search Summary 二分搜索法小結 中的第四類,用子函數當作判斷關系(通常由 mid 計算得出)。判斷的子函數其實就是在整個數組中查找是否存在均有給定邊長的正方形區間,使得其數字和小於等於 threshold。因為此時邊長確定了,只要遍歷左上頂點的位置,然后通過累加和數組快速計算出區間和進行判斷即可,參見代碼如下:
解法二:
class Solution {
public:
int maxSideLength(vector<vector<int>>& mat, int threshold) {
int m = mat.size(), n = mat[0].size(), left = 0, right = min(m, n);
vector<vector<int>> sums(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
sums[i][j] = mat[i - 1][j - 1] + sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
}
}
while (left <= right) {
int mid = left + (right - left) / 2;
if (squareExisted(sums, threshold, mid)) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right;
}
bool squareExisted(vector<vector<int>>& sums, int threshold, int len) {
for (int i = len; i < sums.size(); ++i) {
for (int j = len; j < sums[0].size(); ++j) {
if (sums[i][j] - sums[i - len][j] - sums[i][j - len] + sums[i - len][j - len] <= threshold) return true;
}
}
return false;
}
};
再來看一種更加高效的方法,這種方法在建立累加和的過程中就直接進行判斷了,而且每次只判斷是否有比當前已經存在的正方行邊長大1的區間,有的話就讓結果 res 自增1,因為左上頂點一次只能移動一個位置,不管是向右,還是向下移動,邊長最多也只能增加1。這道題的難點還是在於計算區間時下標的轉換,因為此時的正方形區間的右下頂點為 (i, j),左上頂點為 (i-res, j-res),計算區間和的方法為 sums[i][j] - sums[i - res - 1][j] - sums[i][j - res - 1] + sums[i - res - 1][j - res - 1]
,前提要保證 i - res - 1
和 j - res - 1
均大於等於0,以防止越界,參見代碼如下:
解法三:
class Solution {
public:
int maxSideLength(vector<vector<int>>& mat, int threshold) {
int res = 0, m = mat.size(), n = mat[0].size();
vector<vector<int>> sums(m + 1, vector<int>(n + 1));
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
sums[i][j] = mat[i - 1][j - 1] + sums[i - 1][j] + sums[i][j - 1] - sums[i - 1][j - 1];
if (i - res - 1 >= 0 && j - res - 1 >= 0 && sums[i][j] - sums[i - res - 1][j] - sums[i][j - res - 1] + sums[i - res - 1][j - res - 1] <= threshold) {
++res;
}
}
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1292
參考資料: