【1.7】动态规划-最大的以1为边界的正方形

一、题目

        给你一个由若干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)

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城狮7号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值