「这是我参与2022首次更文挑战的第35天,活动详情查看:2022首次更文挑战」。
题目
给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1:
输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]
示例 2:
输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]
思路
动态规划的题目,但是跟之前做的又不太一样。
状态定义
我们定义状态二维数组dp,dp[i][j]表是坐标为(i,j)的单元格到最近的0的距离。
边界条件
如果原始单元就是0,那么最近的0就是本身,即
dp[i][j] = 0 (mat[i][j] == 0)
状态转移方程
我们可以这样思考,既然需要状态转移,证明当前单元格是1,否则就直接是0了。由于是二维的单元格,所以可以也仅可以从上下左右4个方向上去延伸一步,然后找到上述4个单元格中距离0最近的距离,记为min,那么dp[i][j] = min + 1
dp[i][j] = min(dp[i-1][j], dp[i+1][j], dp[i][j-1], dp[i][j+1])+1 (mat[i][j] == 1)
如上图所示,黄色单元格dp值就是4个相邻绿色单元格dp值中最小值+1
注意点
正常动态规划的题目,做到这里就可以去编码了,毕竟状态定义、边界条件、状态转移方程都有了。但是我们在编码dp的时候就发现了问题,因为无论我从哪个方向遍历矩阵,不可能一次性做到,我要求解dp[i][j]的时候,它的上下左右4个单元格的值都都已经存在了。
所以这里,我们采取的2次遍历的方式:
首先是从左上往右下遍历,这时候求解dp[i][j],它左边和上面的单元格已经求解好了
然后再从右下往左上遍历,这时候求解dp[i][j],它右边和下面的单元格已经求解好了
整合起来,我们就能求解dp的值了。
另外,如果把我们的矩阵大小记为m*n,那么距离最远的2个单元格就是对角线,它们之间的距离为(m-1)+(n-1),记为max。换一种说法,就是任何一个单元格经过max步,一定可以达到这个矩阵中的任意一个单元格。所以,我们初始化dp的值的时候,可以用max+1,由于原始矩阵中至少有1个0,距离小于max+1,这样就肯定会被更新。
Java版本代码
class Solution {
public int[][] updateMatrix(int[][] mat) {
int m = mat.length;
int n = mat[0].length;
int max = (m-1) + (n-1);
int[][] dp = new int[m][n];
for (int i = 0; i < m; i++) {
Arrays.fill(dp[i], max+1);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (mat[i][j] == 0) {
dp[i][j] = 0;
}
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i >= 1) {
dp[i][j] = Integer.min(dp[i][j], dp[i-1][j]+1);
}
if (j >= 1) {
dp[i][j] = Integer.min(dp[i][j], dp[i][j-1]+1);
}
}
}
for (int i = m-1; i >= 0; i--) {
for (int j = n-1; j>= 0; j--) {
if (i < m-1) {
dp[i][j] = Integer.min(dp[i][j], dp[i+1][j]+1);
}
if (j < n-1) {
dp[i][j] = Integer.min(dp[i][j], dp[i][j+1]+1);
}
}
}
return dp;
}
}