leetcode
leetcode 2851 ~ 2900
最大黑方阵

最大黑方阵

难度:

标签:

题目描述

Imagine you have a square matrix, where each cell (pixel) is either black or white Design an algorithm to find the maximum subsquare such that all four borders are filled with black pixels.

Return an array [r, c, size], where rc are the row number and the column number of the subsquare's upper left corner respectively, and size is the side length of the subsquare. If there are more than one answers, return the one that has smallest r. If there are more than one answers that have the same r, return the one that has smallest c. If there's no answer, return an empty array.

Example 1:

Input:
[
   [1,0,1],
   [0,0,1],
   [0,0,1]
]
Output: [1,0,2]
Explanation: 0 represents black, and 1 represents white, bold elements in the input is the answer.

Example 2:

Input:
[
   [0,1,1],
   [1,0,1],
   [1,1,0]
]
Output: [0,0,1]

Note:

  • matrix.length == matrix[0].length <= 200

代码结果

运行时间: 76 ms, 内存: 17.7 MB


/*
 * 思路:
 * 1. 使用两个辅助矩阵left和up,left[i][j]表示从(i, j)往左数连续的黑色像素的数量,up[i][j]表示从(i, j)往上数连续的黑色像素的数量。
 * 2. 遍历矩阵,更新left和up矩阵。
 * 3. 使用动态规划的方法,dp[i][j]表示以(i, j)为右下角的最大子方阵的边长。
 * 4. 如果left[i][j]和up[i][j]都大于等于某个边长,则该位置可能是一个满足条件的子方阵的右下角。
 * 5. 找到最大的dp[i][j],记录其左上角坐标和边长。
 */

import java.util.stream.IntStream;

public class Solution {
    public int[] findSquare(int[][] matrix) {
        int n = matrix.length;
        if (n == 0) return new int[0];

        int[][] left = new int[n][n];
        int[][] up = new int[n][n];

        IntStream.range(0, n).forEach(i -> 
            IntStream.range(0, n).forEach(j -> {
                if (matrix[i][j] == 0) {
                    left[i][j] = (j == 0 ? 0 : left[i][j - 1]) + 1;
                    up[i][j] = (i == 0 ? 0 : up[i - 1][j]) + 1;
                }
            })
        );

        int maxSize = 0;
        int[] result = new int[0];

        for (int i = n - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                int size = Math.min(left[i][j], up[i][j]);
                while (size > maxSize) {
                    if (left[i - size + 1][j] >= size && up[i][j - size + 1] >= size) {
                        maxSize = size;
                        result = new int[]{i - size + 1, j - size + 1, size};
                    }
                    size--;
                }
            }
        }

        return result;
    }
}

解释

方法:

这个题解使用动态规划的思路解决问题。首先,定义两个矩阵 row 和 col,其中 row[i][j] 表示从 (i, j) 位置向左延伸的连续黑色像素的最大数目,col[i][j] 表示从 (i, j) 位置向上延伸的连续黑色像素的最大数目。然后,对于每个为黑色的像素 (即 matrix[i][j] == 0),计算以该点为右下角的最大可能的黑色子方阵。为此,获取从该点向左和向上延伸的黑色像素的最小值(因为方阵需要两边长度相等),并验证这一长度是否可以构成一个有效的子方阵。验证方法是检查顶部水平边和左侧垂直边是否同样拥有至少这么长的连续黑色像素。如果找到更大的黑色子方阵,更新结果数组 res。这种方法确保了最终找到的是最大的且最左上角的黑色子方阵。

时间复杂度:

O(m*n*min(m, n))

空间复杂度:

O(m*n)

代码细节讲解

🦆
为什么在更新 row 和 col 数组时只考虑 matrix[i][j] 为 0 的情况?
在这个问题中,我们寻找的是最大的全黑色子方阵,即方阵内的所有像素都是黑色(值为0)。因此,只有当 `matrix[i][j]` 为 0 时,该位置才可能是某个黑色子方阵的一部分。如果 `matrix[i][j]` 是 1,即白色像素,那么它不能是任何黑色子方阵的一部分,从这个位置开始向左或向上的连续黑色像素数自然为 0。更新 `row` 和 `col` 数组时忽略白色像素是因为它们不会对寻找最大黑色子方阵产生贡献。
🦆
在实现中,为什么要从当前可能的最大边长开始向下逐渐检查直到边长 1,这种方法与直接使用最小边长有什么不同?
从当前可能的最大边长开始向下逐渐检查的策略是为了尽快找到最大的子方阵。因为我们的目标是找到最大的黑色子方阵,如果从最大边长开始检查,并且立即找到一个有效的方阵,就可以直接确定这是当前位置可以形成的最大方阵,无需检查更小的方阵。如果从最小边长开始检查,则可能需要多次验证直到找到最大边长,这样做效率较低。此外,从大到小的检查可以在发现较大的子方阵时立即停止进一步检查,从而提高算法的执行效率。
🦆
题解中提到,如果找到更大的黑色子方阵,就更新结果数组 res。这种更新策略是否保证了最终 res 中的方阵是最左上角的?如果有多个相同大小的最大方阵,它会怎样选择?
在题解的实现中,由于是按照从左到右、从上到下的顺序遍历矩阵的每个元素,并且只有在找到更大的方阵时才更新结果数组 `res`,所以这种策略确保了如果有多个相同大小的最大方阵时,选择的是最左上角的方阵。这是因为一旦在某个位置找到了目前为止的最大方阵,随后遍历到的任何具有相同边长的方阵都不会更新结果,除非它们有更大的边长。因此,这种更新策略确保了最终的结果是最左上角的最大方阵。

相关问题