「这是我参与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;
}
}