一、题目
给你一个由若干0和1组成的二维网格grid,请你找出
边界
全部由1组成的最大正方
形子网格
,并返回该子网格中的元素数量。如果不存在,则返回0。
提示:
1)1 <= grid.length <= 100
2)1 <= grid[0].length <= 100
3)grid [i] [j] 为0或1
二、求解思路
在前文中,我们探讨了要求使用动态规划来求解最大正方形问题。在上一个题目,正方形的所有单元格必须全部为1,一旦掌握了动态规划的核心思想,编写代码就会变得相当简洁。相比之下,当前这道题目则放宽了条件,仅要求正方形的四周边缘单元格为1,而内部单元格的数值则不予考虑。因此,这道题的难度略高于上一题。
解题策略如下:
首先,我们需要计算每个单元格在其横向和竖向方向上连续出现的1的数量。
接着,我们遍历整个二维网格,以每个单元格作为潜在正方形的右下角,分别确定其上方和左侧连续1的数量,并取这两者的最小值作为正方形的初始边长。然后,我们检查正方形的左侧和上方边缘的长度是否均不小于该边长,如果是,则更新最大正方形的边长;如果不是,则减小正方形的边长并继续进行检查。
如果上述解释让你感到困惑,请不要担心,我们将逐步进行分析。当你完成整个分析过程并回过头来审视时,你会发现其实一切都非常直观明了。
1、第一步,计算横向和竖向连续1的个数,举个例子。
代码比较简单,我们定义一个三维数组,其中
dp[i][j][0]: (i , j)横向连续1的个数
dp[i][j][1]: (i , j)竖向连续1的个数
我们计算的时候,如果当前位置是0就跳过,
只有是1的时候才计算
,分别统计左边
和上边(也就是横向和竖向)连续1的个数。代码比较简单,我们来看下(这里为了减少一些边界条件的判断,把dp的宽和高都增加了1)。
#include <vector>
int m = grid.size();
int n = grid[0].size();
// dp[i][j][0]: (i,j)横向连续1的个数
// dp[i][j][1]: (i,j)竖向连续1的个数
std::vector<std::vector<std::vector<int>>> dp(m + 1, std::vector<std::vector<int>>(n + 1, std::vector<int>(2, 0)));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果当前位置是0,就跳过
if (grid[i - 1][j - 1] == 0)
continue;
// 如果是1,我们就计算横向和竖向连续1的个数
dp[i][j][0] = dp[i][j - 1][0] + 1;
dp[i][j][1] = dp[i - 1][j][1] + 1;
}
}
2、第二步,找出正方形的最大边长
我们会以网格中的每一个位置为正方形的右下角,来找出正方形的边长。如下图所
示,我们以橙色的位置1为正方形的右下角,分别沿着
左边和上边找出他们连续1的
个数
,
最小
的作为正方形的边长
。因为左边和上边连续1的个数我们在第一步的时
候 已 经 计 算 过 , 分 别 是 dp[i][j][0] 和 dp[i][j][1] , 也 就 是 正 方 形 的 边 长 我 们
暂时
可以认为是
int curSide = std::min(dp[i][j][0], dp[i][j][1]);
其实大家已经看到了这个边长就是正方形下边和右边的长度,但是正方形的上边和
左边我们还没确定,我们继续确定正方形左边和上边的长度。会有两种情况,一种如下图所示,就是正方形左边和上边的长度都大于curSide,我们可以认为以
坐标(i , j)为右下角的正方形的最大长度就是curSide。
另一种如下图所示,正方形上边的长度是1,小于curSide。
这 种 情 况 下 是 构 不 成 正 方 形 的 , 所 以 我 们 要 缩 小 curSide 的 值 , 然 后 再 继 续 判
断。
三、代码实现
理解了上述步骤后,编写代码就变得轻而易举了。现在,让我们直接深入代码部分。
#include <iostream>
#include <vector>
#include <algorithm>
int largest1BorderedSquare(std::vector<std::vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
// dp[i][j][0]: (i,j)横向连续1的个数
// dp[i][j][1]: (i,j)竖向连续1的个数
std::vector<std::vector<std::vector<int>>> dp(m + 1, std::vector<std::vector<int>>(n + 1, std::vector<int>(2, 0)));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 如果当前位置是0,就跳过
if (grid[i - 1][j - 1] == 0)
continue;
// 如果是1,我们就计算横向和竖向连续1的个数
dp[i][j][0] = dp[i][j - 1][0] + 1;
dp[i][j][1] = dp[i - 1][j][1] + 1;
}
}
int maxSide = 0; // 记录正方形的最大长度
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
// 沿着当前坐标往上和往左找出最短的距离,暂时看做是正方形的边长(正方形的具体边长
// 还要看上边和左边的长度,所以这里要判断一下)
int curSide = std::min(dp[i][j][0], dp[i][j][1]);
// 如果边长小于maxSide,即使找到了也不可能再比maxSide大,所以我们没必要再找,直接跳过,
if (curSide <= maxSide)
continue;
// curSide可以认为是正方形下边和右边的长度,我们还需要根据正方形上边和左边的长度
// 来确认是否满足正方形的条件
for (; curSide > maxSide; curSide--) {
// 判断正方形的左边和上边的长度是否大于curSide,如果不大于,我们就缩小正方形
// 的长度curSide,然后继续判断
if (dp[i][j - curSide + 1][1] >= curSide && dp[i - curSide + 1][j][0] >= curSide) {
maxSide = curSide;
// 更短的就没必要考虑了,这里直接中断
break;
}
}
}
}
// 返回正方形的边长
return maxSide * maxSide;
}
int main() {
std::vector<std::vector<int>> grid = {
{1, 1, 1},
{1, 0, 1},
{1, 1, 1}
};
int result = largest1BorderedSquare(grid);
std::cout << "The area of the largest 1-bordered square is: " << result << std::endl;
return 0;
}
时间复杂度
:
O(m*n*min(m,n))
,
m
和
n
分别是矩阵的宽和高
空间复杂度
:
O(m*n)
,使用了一个三维数组
(m*n*2)