【算法-LeetCode】221. 最大正方形(动态规划;空间优化)

221. 最大正方形 - 力扣(LeetCode)

文章更新:2021年10月23日13:42:02

问题描述及示例

在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。

示例 1:

输入:matrix = [[“1”,“0”,“1”,“0”,“0”],[“1”,“0”,“1”,“1”,“1”],[“1”,“1”,“1”,“1”,“1”],[“1”,“0”,“0”,“1”,“0”]]
输出:4

示例 2:

输入:matrix = [[“0”,“1”],[“1”,“0”]]
输出:1

示例 3:
输入:matrix = [[“0”]]
输出:0

提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 300
matrix[i][j] 为 ‘0’ 或 ‘1’

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximal-square
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的题解(动态规划)

这道题题目的动态规划味道还是比较明显的。有关动态规划的思路总结,可以参看下方的博客,里面也有我目前做的其他动态规划题目:

参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客

本题大致思路如下:

①确定 dp[i][j] 的含义。
dp[i][j] 是指在 matrix 中以元素 [i][j] 为右下角元素的正方形的边长。

②确定状态转移方程。
遍历 matrix

  1. 如果当前遍历元素为 '1',那么就在 dp 数组中对应位置处取当前元素的左上方(即 dp[i-1][j-1])、正上方(即 dp[i-1][j])、正左方(即 dp[i][j-1])这三个元素中的最小值,并将该最小值加 1 作为当前 dp[i][j] 的值。
  2. 如果当前遍历元素为 '0',那么就取当前 dp[i][j] 的值为 0

【算法-LeetCode】221. 最大正方形(动态规划;空间优化)_第1张图片

③完成 dp 数组的初始化。
由上面的状态转移方程可知,需要对 dp 数组的第一行和第一列做初始化。按理来说应该对 dp 数组的第一行和第一列单独求值,但是为了省去这些单独的操作,我们可以给 dp 数组分别加上一个辅助行和辅助列。并且将这些辅助行和列的值全部填充为 0。这样就可以统一逻辑。

接下来就是由上到下,由前到后地遍历 martix 并填充 dp 数组了。

详解请看下方注释:

/**
 * @param {character[][]} matrix
 * @return {number}
 */
var maximalSquare = function(matrix) {
  // width用于存储当前矩阵中的最大正方形的边长,初始值应为0
  let width = 0;
  // 创建二维dp数组,注意其长宽均比matrix大1,且其元素的值均被初始化为0,
  // 这就省去了单独初始化首行和首列的操作
  let dp = Array.from({length: matrix.length+1}).map(
    () => Array.from({length: matrix[0].length+1}).fill(0)
  );
  // 开始遍历matrix,或者说开始填充dp数组,注意,i和j的值都是从1开始的
  for(let i = 1; i <= matrix.length; i++) {
    for(let j = 1; j <= matrix[0].length; j++) {
      // 如果当前遍历的元素的值为 “1”,那么就在dp数组中的对应位置的左上方、正上方、正左方
      // 这三个元素中选取最小值,该最小值加1即为当前dp[i][j]的值
      if(matrix[i-1][j-1] === "1") {
        dp[i][j] = Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1;
        // 更新dp[i][j]后,还要更新width的值为最长
        width = Math.max(dp[i][j], width);
      }
    }
  }
  // 最后返回最大正方形的面积
  return width * width;
};


提交记录
75 / 75 个通过测试用例
状态:通过
执行用时:88 ms, 在所有 JavaScript 提交中击败了55.44%的用户
内存消耗:41.2 MB, 在所有 JavaScript 提交中击败了41.89%的用户
时间:2021/10/23 13:47

动态规划的空间优化

因为本题中 dp[i][j] 的取值都是来自其左方和上方区域,所以可以用上我之前总结的空间优化思路来节省内存消耗,详细解释可参看下方博客:

参考:【算法-LeetCode】1143. 最长公共子序列(动态规划;滚动数组;通用的空间优化)_赖念安的博客-CSDN博客

本题也是一样的套路,将二维 dp 数组的宽度固定为 2,而把其长度设置为 matrix[0] 的长度。dp[1] 就是被滚动的那个。

/**
 * @param {character[][]} matrix
 * @return {number}
 */
var maximalSquare = function(matrix) {
  let width = 0;
  let dp = Array.from({length: 2}).map(
    () => Array.from({length: matrix[0].length+1}).fill(0)
  );

  for(let i = 1; i <= matrix.length; i++) {
    for(let j = 1; j <= matrix[0].length; j++) {
      if(matrix[i-1][j-1] === "1") {
        dp[1][j] = Math.min(dp[0][j-1], dp[0][j], dp[1][j-1]) + 1;
        width = Math.max(dp[1][j], width);
      }
    }
    // 其他的逻辑都和上面的差不多,关键是下面的滚动操作,将dp数组第二行的数据复制到第一行,
    // 同时将第二行的数据重新初始化为0(注意重置为0这一步不能省略,详看下方【补充1】)
    dp[0] = [...dp[1]];
    dp[1].fill(0);
  }
  return width * width;
};


提交记录
75 / 75 个通过测试用例
状态:通过
执行用时:84 ms, 在所有 JavaScript 提交中击败了70.23%的用户
内存消耗:40.3 MB, 在所有 JavaScript 提交中击败了93.43%的用户
时间:2021/10/23 14:38	

可以看到,这种解法的空间表现还是有比较大的提升的。

相关补充

【补充1】
如果没有将 dp 数组第二行重置为 0,那么就会影响后续的 dp 数组元素的填充:

【算法-LeetCode】221. 最大正方形(动态规划;空间优化)_第2张图片
因为在状态转移方程中没有对 matrix[i-1][j-1] === "0" 的情况做判断,按理来说,应该要将该情况下的 dp[i][j] 设置为 0,但是我们没有这样设置,此时如果不进行重置操作,那么就会对后续的 dp[i][j] 取值造成影响。

官方题解

更新:2021年7月29日18:43:21

因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。

更新:2021年10月23日13:51:50

参考:最大正方形 - 最大正方形 - 力扣(LeetCode)

【更新结束】

有关参考

更新:2021年10月23日13:52:33
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客
参考:【JavaScript基础-二维数组】JavaScript修改二维数组的某个元素时,其上下元素也受到影响_赖念安的博客-CSDN博客
更新:2021年10月23日14:43:58
参考:【算法-LeetCode】1143. 最长公共子序列(动态规划;滚动数组;通用的空间优化)_赖念安的博客-CSDN博客

你可能感兴趣的:(LeetCode,算法,动态规划,leetcode,空间优化,javascript)