题目描述(中等难度)

221. Maximal Square - 图1

输出包含 1 的最大正方形面积。

解法一 暴力

参考 85 题 解法一,85 题是求包含 1 的最大矩形,这道题明显只是 85 题的一个子问题了,85 题的解法稍加修改就能写出这道题了,下边讲一下 85 题 的思路。

参考这里-solution-for-your-reference>),遍历每个点,求以这个点为矩阵右下角的所有矩阵面积。如下图的两个例子,橙色是当前遍历的点,然后虚线框圈出的矩阵是其中一个矩阵。

221. Maximal Square - 图2

怎么找出这样的矩阵呢?如下图,如果我们知道了以这个点结尾的连续 1 的个数的话,问题就变得简单了。

221. Maximal Square - 图3

  1. 首先求出高度是 1 的矩形面积,也就是它自身的数,也就是上图以橙色的 4 结尾的 「1234」的那个矩形,面积就是 4。
  2. 然后向上扩展一行,高度增加一,选出当前列最小的数字,作为矩阵的宽,如上图,当前列中有 24 ,那么就将 2 作为矩形的宽,求出面积,对应上图的矩形圈出的部分。
  3. 然后继续向上扩展,重复步骤 2。

按照上边的方法,遍历所有的点,以当前点为矩阵的右下角,求出所有的矩阵就可以了。下图是某一个点的过程。

以橙色的点为右下角,高度为 1。

221. Maximal Square - 图4

高度为 2。

221. Maximal Square - 图5

高度为 3。

221. Maximal Square - 图6

代码的话,把求每个点累计的连续 1 的个数用 width 保存,同时把求最大矩形的面积和求 width融合到同一个循环中。

下边是 85 题 的代码。

  1. public int maximalRectangle(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. //保存以当前数字结尾的连续 1 的个数
  6. int[][] width = new int[matrix.length][matrix[0].length];
  7. int maxArea = 0;
  8. //遍历每一行
  9. for (int row = 0; row < matrix.length; row++) {
  10. for (int col = 0; col < matrix[0].length; col++) {
  11. //更新 width
  12. if (matrix[row][col] == '1') {
  13. if (col == 0) {
  14. width[row][col] = 1;
  15. } else {
  16. width[row][col] = width[row][col - 1] + 1;
  17. }
  18. } else {
  19. width[row][col] = 0;
  20. }
  21. //记录所有行中最小的数
  22. int minWidth = width[row][col];
  23. //向上扩展行
  24. for (int up_row = row; up_row >= 0; up_row--) {
  25. int height = row - up_row + 1;
  26. //找最小的数作为矩阵的宽
  27. minWidth = Math.min(minWidth, width[up_row][col]);
  28. //更新面积
  29. maxArea = Math.max(maxArea, height * minWidth);
  30. }
  31. }
  32. }
  33. return maxArea;
  34. }

我们先在上边的代码基础上,把这道题做出来,我把修改的地方标记出来了。下边的代码一定程度上已经做了一些优化,把能提前结束的地方提前结束了。

  1. public int maximalSquare(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. //保存以当前数字结尾的连续 1 的个数
  6. int[][] width = new int[matrix.length][matrix[0].length];
  7. int maxArea = 0;
  8. /************修改的地方*****************/
  9. int maxHeight = 0; //记录当前正方形的最大边长
  10. /*************************************/
  11. //遍历每一行
  12. for (int row = 0; row < matrix.length; row++) {
  13. for (int col = 0; col < matrix[0].length; col++) {
  14. // 更新 width
  15. if (matrix[row][col] == '1') {
  16. if (col == 0) {
  17. width[row][col] = 1;
  18. } else {
  19. width[row][col] = width[row][col - 1] + 1;
  20. }
  21. } else {
  22. width[row][col] = 0;
  23. }
  24. // 记录所有行中最小的数
  25. int minWidth = width[row][col];
  26. /************修改的地方*****************/
  27. if(minWidth <= maxHeight){
  28. continue;
  29. }
  30. /*************************************/
  31. // 向上扩展行
  32. for (int up_row = row; up_row >= 0; up_row--) {
  33. int height = row - up_row + 1;
  34. // 找最小的数作为矩阵的宽
  35. minWidth = Math.min(minWidth, width[up_row][col]);
  36. /************修改的地方*****************/
  37. //因为我们找正方形,当前高度大于了最小宽度,可以提前结束
  38. if(height > minWidth){
  39. break;
  40. }
  41. // 只有是正方形的时候才更新面积
  42. if (height == minWidth) {
  43. maxArea = Math.max(maxArea, height * minWidth);
  44. maxHeight = Math.max(maxHeight, height);
  45. break;
  46. }
  47. /*************************************/
  48. }
  49. }
  50. }
  51. return maxArea;
  52. }

当然因为我们只考虑正方形,我们可以抛开原来的代码,只参照之前的思路写一个新的代码。

首先因为正方形的面积是边长乘边长,所以上边的 maxArea 是没有意义的,我们只记录最大边长即可。然后是其它细节的修改,让代码更简洁,代码如下。

  1. public int maximalSquare(char[][] matrix) {
  2. if (matrix.length == 0) {
  3. return 0;
  4. }
  5. // 保存以当前数字结尾的连续 1 的个数
  6. int[][] width = new int[matrix.length][matrix[0].length];
  7. // 记录最大边长
  8. int maxSide = 0;
  9. // 遍历每一行
  10. for (int row = 0; row < matrix.length; row++) {
  11. for (int col = 0; col < matrix[0].length; col++) {
  12. // 更新 width
  13. if (matrix[row][col] == '1') {
  14. if (col == 0) {
  15. width[row][col] = 1;
  16. } else {
  17. width[row][col] = width[row][col - 1] + 1;
  18. }
  19. } else {
  20. width[row][col] = 0;
  21. }
  22. // 当前点作为正方形的右下角进行扩展
  23. int curWidth = width[row][col];
  24. // 向上扩展行
  25. for (int up_row = row; up_row >= 0; up_row--) {
  26. int height = row - up_row + 1;
  27. if (width[up_row][col] <= maxSide || height > curWidth) {
  28. break;
  29. }
  30. maxSide = Math.max(height, maxSide);
  31. }
  32. }
  33. }
  34. return maxSide * maxSide;
  35. }

解法二 动态规划

写出解法一,也没有往别的地方想了,参考 这里,很典型的动态规划的问题了。

解法一中我们求每个点的最大边长时,没有考虑到之前的解,事实上之前的解完全可以充分利用。

dp[i][j] 表示以 matrix[i][j] 为右下角正方形的最大边长。那么递推式如下。

初始条件,那就是第一行和第一列的 dp[i][j] = matrix[i][j] - '0',也就意味着 dp[i][j] 要么是 0 要么是 1

然后就是递推式。

dp[i][j] = Min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]) + 1

也就是当前点的左边,上边,左上角的三个点中选一个最小值,然后加 1

首先要明确 dp[i][j] 表示以 matrix[i][j] 为右下角的正方形的最大边长。

然后我们从当前点向左和向上扩展,可以参考下边的图。

221. Maximal Square - 图7

向左最多能扩展多少呢?dp[i][j-1]dp[i-1][j-1],当前点左边和左上角选一个较小的。也就是它左边最大的正方形和它左上角最大的正方形的,边长选较小的。

向上能能扩展多少呢?dp[i-1][j]dp[i-1][j-1],当前点上边和左上角选一个较小的。也就是它上边最大的正方形和它左上角最大的正方形,边长选较小的。

然后向左扩展和向上扩展两个最小值中再选一个较小的,最后加上 1 就是最终的边长了。

最终其实是从三个正方形中最小的边长。

221. Maximal Square - 图8

代码的话,使用个技巧,那就是行和列多申请一行,这样的话第一行和第一列的情况就不需要单独考虑了。

  1. public int maximalSquare(char[][] matrix) {
  2. int rows = matrix.length;
  3. if (rows == 0) {
  4. return 0;
  5. }
  6. int cols = matrix[0].length;
  7. int[][] dp = new int[rows + 1][cols + 1];
  8. int maxSide = 0;
  9. for (int i = 1; i <= rows; i++) {
  10. for (int j = 1; j <= cols; j++) {
  11. //因为多申请了一行一列,所以这里下标要减 1
  12. if (matrix[i - 1][j - 1] == '0') {
  13. dp[i][j] = 0;
  14. } else {
  15. dp[i][j] = Math.min(dp[i - 1][j], Math.min(dp[i][j - 1], dp[i - 1][j - 1])) + 1;
  16. maxSide = Math.max(dp[i][j], maxSide);
  17. }
  18. }
  19. }
  20. return maxSide * maxSide;
  21. }

然后又是动态规划的经典操作了,空间复杂度的优化,之前也遇到很多了,这里不细讲了。因为更新当前行的时候,只用到前一行的信息,之前的行就没有再用到了,所以我们可以用一维数组,不需要二维矩阵。

把图画出来就可以理解出来各个变量的关系了,这里偷懒就不画了。第一次遇到空间复杂度的优化是 第 5 题 ,写的比较详细,大家可以看看。后边基本上遇到动态规划,就会考虑空间复杂度的优化,很多很多了。可以在 https://leetcode.wang/ 搜索动态规划做一做。

221. Maximal Square - 图9

下边是空间复杂度优化的代码,最关键的是用 pre 保存了左上角的值。

  1. public int maximalSquare(char[][] matrix) {
  2. int rows = matrix.length;
  3. if (rows == 0) {
  4. return 0;
  5. }
  6. int cols = matrix[0].length;
  7. int[] dp = new int[cols + 1];
  8. int maxSide = 0;
  9. int pre = 0;
  10. for (int i = 1; i <= rows; i++) {
  11. for (int j = 1; j <= cols; j++) {
  12. int temp = dp[j];
  13. if (matrix[i - 1][j - 1] == '0') {
  14. dp[j] = 0;
  15. } else {
  16. dp[j] = Math.min(dp[j - 1], Math.min(dp[j], pre)) + 1;
  17. maxSide = Math.max(dp[j], maxSide);
  18. }
  19. pre = temp;
  20. }
  21. }
  22. return maxSide * maxSide;
  23. }

解法一的话是受之前解法的启发,解法二的话算是动态规划的经典应用了,通过之前的解更新当前的解。这里的空间复杂度优化需要多加一个变量来辅助,算是比较难的了。

windliang wechat

添加好友一起进步~

如果觉得有帮助的话,可以点击 这里 给一个 star 哦 ^^

如果想系统的学习数据结构和算法,强烈推荐一个我之前学过的课程,可以点击 这里 查看详情