1.题目
给定一个方阵,其中每个单元(像素)非黑即白。设计一个算法,找出 4 条边皆为黑色像素的最大子方阵。
返回一个数组 [r, c, size] ,其中 r, c 分别代表子方阵左上角的行号和列号,size 是子方阵的边长。若有多个满足条件的子方阵,返回 r 最小的,若 r 相同,返回 c 最小的子方阵。若无满足条件的子方阵,返回空数组。
示例 1:
输入:
[
[1,0,1],
[0,0,1],
[0,0,1]
]
输出: [1,0,2]
解释: 输入中 0 代表黑色,1 代表白色,标粗的元素即为满足条件的最大子方阵
示例 2:
输入:
[
[0,1,1],
[1,0,1],
[1,1,0]
]
输出: [0,0,1]
提示:
matrix.length == matrix[0].length <= 200
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-black-square-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2.题解
从矩阵右下角开始逐层向上更新每个元素向下和向右的连续1的个数。(动态规划)
过程中记录最大的方形边长和元素位置。
class Solution {
public:
vector<int> findSquare(vector<vector<int>>& matrix) {
vector<int> ans(3, 0);
int n = matrix.size();
if(n == 0) return {};
if(n == 1){
if(matrix[0][0] == 0)
return {0, 0, 1};
else
return {};
}
//cnt[r][c][0/1],0右侧,1下侧
vector<vector<vector<int>>> cnt(n, vector<vector<int>>(n, vector<int>(2)));
for(int r = n-1; r >= 0; r--){
for(int c = n-1; c >= 0; c--){
if(matrix[r][c] == 1)
cnt[r][c][0] = cnt[r][c][1] = 0;
else{
//统计cnt[r][c][0/1]
if(r < n-1) cnt[r][c][1] = cnt[r+1][c][1] + 1;
else cnt[r][c][1] = 1;
if(c < n-1) cnt[r][c][0] = cnt[r][c+1][0] + 1;
else cnt[r][c][0] = 1;
//更新当前最大子方阵
int len = min(cnt[r][c][0], cnt[r][c][1]);//最大的可能的边长
while(len >= ans[2]){//要答案r,c最小,所以带等号
if(cnt[r+len-1][c][0] >= len && cnt[r][c+len-1][1] >= len){
//可以构成长为len的方阵
ans = {r, c, len};
break;
}
len--;
}
}
}
}
return ans;
}
};
链接:https://leetcode-cn.com/problems/max-black-square-lcci/solution/c-dong-tai-gui-hua-by-tmoonli/
下面这个效率更高,更节省空间。(思路都一样)
class Solution {
public:
vector<int> findSquare(vector<vector<int>>& matrix) {
int R = matrix.size();
if(R == 0) return {};
// 当前位置向左连续0的个数
int fL[R][R];
memset(fL, 0x00, sizeof fL);
// 当前位置向上连续0的个数
int fU[R][R];
memset(fU, 0x00, sizeof fU);
// 初始化
for(int i=0; i<R; ++i){
for(int j=0; j<R; ++j){
if(matrix[i][j]==0){
if(i==0 && j==0){
fU[i][j]=1;
fL[i][j]=1;
}
else if(i==0 && j>0){
fU[i][j] = 1;
fL[i][j] = fL[i][j-1]+1;
}
else if(j==0 && i>0){
fL[i][j] = 1;
fU[i][j] = fU[i-1][j]+1;
}
else if(i>0 && j>0){
fL[i][j] = fL[i][j-1]+1;
fU[i][j] = fU[i-1][j]+1;
}
}
}
}
vector<int> res;
for(int i=R-1; i>=0; --i){
for(int j=R-1; j>=0; --j){
int L = min(fL[i][j], fU[i][j]);
for(int k=L; k>0; --k){
if(res.size()>0 && k<res[2]) break;
// 左上角顶点
int px = i-k+1;
int py = j-k+1;
// 检测另外两条边能否构成正方形
if(fL[px][j]>=k && fU[i][py]>=k){
if(res.size()==0 || px<res[0] || py<res[1])
res = {px, py, k};
}
}
}
}
return res;
}
};
其实if(res.size()==0 || px<res[0] || py<res[1])这行代码可以删除,因为题目要求,若有多个满足条件的子方阵,返回 r 最小的,若 r 相同,返回 c 最小的子方阵。
我们是按照相反方向的进行遍历,只要进行更新,就是符合题目要求的。