leetcode-最大加号标志

483 阅读4分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。

连续10天leetcode,也算是一个小小里程碑。这段时间一直在做动态规划的题目,找到感觉后,做起来会快很多。

题目

在一个大小在 (0, 0) 到 (N-1, N-1) 的2D网格 grid 中,除了在 mines 中给出的单元为 0,其他每个单元都是 1。网格中包含 1 的最大的轴对齐加号标志是多少阶?返回加号标志的阶数。如果未找到加号标志,则返回 0。

一个 k" 阶由 1 组成的“轴对称”加号标志具有中心网格  grid[x][y] = 1 ,以及4个从中心向上、向下、向左、向右延伸,长度为 k-1,由 1 组成的臂。下面给出 k" 阶“轴对称”加号标志的示例。注意,只有加号标志的所有网格要求为 1,别的网格可能为 0 也可能为 1。

k 阶轴对称加号标志示例:
阶 1:
0 0 0
0 1 0
0 0 0

阶 2:
0 0 0 0 0
0 0 1 0 0
0 1 1 1 0
0 0 1 0 0
0 0 0 0 0

阶 3:
0 0 0 0 0 0 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 1 1 1 1 1 0
0 0 0 1 0 0 0
0 0 0 1 0 0 0
0 0 0 0 0 0 0

示例 1:
输入: N = 5, mines = [[4, 2]]
输出: 2
解释:
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 1 1 1
1 1 0 1 1
在上面的网格中,最大加号标志的阶只能是2。一个标志已在图中标出。

示例 2:
输入: N = 2, mines = []
输出: 1
解释:
1 1
1 1
没有 2 阶加号标志,有 1 阶加号标志。

示例 3:
输入: N = 1, mines = [[0, 0]]
输出: 0
解释:
0
没有加号标志,返回 0 。

思路

这题有点意思,其实是求01矩阵中1组成的最大加号。一开始没什么思路,就从暴力方法入手,先思考如果直接暴力解会怎么做。这样自然能想到的是:遍历整个矩阵,如果当前点是0,直接跳过,如果当前点是1,那么至少已经是1个1阶的加号了,然后分别向上、下、左、右4个方向延伸1,得到4个方向的最大长度为l、r、t、d,那么以当前点为中心点的最大加号阶数就是min(l, r, t, d)。有了这个暴力方法做基础,相当于定于出了状态,接下来我们再思考怎么做状态压缩。其实对每个点,4个方向上的长度都是跟前一个元素的同方向长度相关的,我们从最容易理解的向右方向来看,如果当前元素为0,那么向右的长度为0,如果当前是1,向右的长度就等于左边一个点的向右长度+1,例如下面这个矩阵:

0 1 1 1
1 0 1 1
0 1 1 1
1 1 1 1
对应的每个点向右长度的矩阵为: 0 1 2 3
1 0 1 2
0 1 2 3
1 2 3 4
同理可以得到向左、向下、向上这3个方向的长度矩阵 0 3 2 1
1 0 2 1
0 3 2 1
4 3 2 1

0 1 1 1
1 0 2 2
0 1 3 3
1 1 4 4

0 1 4 4
1 0 3 3
0 2 2 2
1 1 1 1

求出这几个矩阵后,可以快速求得以某个点中心点的最大加号阶数就是min(l, r, t, d),然后遍历矩阵,求出最大值即可。

当然,实际编码的时候,还可以采取一些优化:

  • 因为求一个方向的矩阵时,每个点的值只跟本身和它前一个点的值有关,所以可以定义一个临时变量current存储前一个节点的值
  • 而且,最后每个点的结果是min(l, r, t, d),所以,不需要存储这4个方向对应的4个矩阵的值,只要定义一个矩阵存储每个点的min值即可
  • 最后,遍历结果矩阵求最大值的步骤,可以放在求最后一个方法的矩阵里面,也能减少一次n*n的矩阵遍历

Java版本代码

class Solution {
    public int orderOfLargestPlusSign(int n, int[][] mines) {
        int ans = 0;
        // 因为布尔类型默认是false,所以这里可以把01反过来,不需要重新初始化成1,mines部分初始化成1就好
        boolean[][] m = new boolean[n][n];
        for (int[] point : mines) {
            m[point[0]][point[1]] = true;
        }
        int[][] dp = new int[n][n];
        for (int i = 0; i < n; i++) {
            // 向右
            int current = 0;
            for (int j = 0; j < n; j++) {
                if (m[i][j]) {
                    current = 0;
                    dp[i][j] = 0;
                } else {
                    current = current + 1;
                    dp[i][j] = current;
                }
            }
            // 向左
            current = 0;
            for (int j = n-1; j >= 0; j--) {
                if (m[i][j]) {
                    current = 0;
                    dp[i][j] = 0;
                } else {
                    current = current + 1;
                    dp[i][j] = Integer.min(dp[i][j], current);
                }
            }
        }
        for (int j = 0; j < n; j++) {
            // 向下
            int current = 0;
            for (int i = 0; i < n; i++) {
                if (m[i][j]) {
                    current = 0;
                    dp[i][j] = 0;
                } else {
                    current = current + 1;
                    dp[i][j] = Integer.min(dp[i][j], current);
                }
            }
            // 向上
            current = 0;
            for (int i = n-1; i >= 0; i--) {
                if (m[i][j]) {
                    current = 0;
                    dp[i][j] = 0;
                } else {
                    current = current + 1;
                    dp[i][j] = Integer.min(dp[i][j], current);
                }
                // 最后一次对全矩阵的遍历,顺便可以把ans求出来
                ans = Integer.max(ans, dp[i][j]);
            }
        }
        return ans;
    }
}