一刷302-剑指 Offer II 107. 矩阵中的距离(542. 01 矩阵)

题目:
给定一个由 01 组成的矩阵 mat ,请输出一个大小相同的矩阵,
其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。

两个相邻元素间的距离为 1

在这里插入图片描述

输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
输出:[[0,0,0],[0,1,0],[0,0,0]]

在这里插入图片描述

输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]

提示:
m == mat.length
n == mat[i].length
1 <= m, n <= 104
1 <= m * n <= 104
mat[i][j] is either 0 or 1.
mat 中至少有一个 0 
------------------------
思路:
如果想要理解两次DP的正确性,请先确保自己已经懂得了四方向DP的解法。
首先我们定义dp[i][j] 为mat[i][j]0 的最短距离,显然我们有以下状态转移方程:

dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i+1][j], dp[i][j+1]) + 1, mat[i][j] = 1
dp[i][j] = 0, mat[i][j] = 0

然后需要说明的是,双向DP指的是从左上到右下和从右下到左上分别运行一次动态规划算法,
运行后便得到了答案(另一条对角线也可以)。

再则需要说明的是,双向DP的理解难点在哪里?前面我们提到从左上到右下动态规划,
那么此时dp[i][j]表示的是mat[i][j]到其左上部分0的最短距离;
从右下到左上动态规划,dp[i][j]表示的是mat[i][j]到其右下部分0的最短距离,
那么从字面上看有一个很直接的疑惑,
mat[i][j]到其右上部分或者左下部分0的距离有没有被考虑到呢?

下面我们来回答这个问题,先给出答案:都考虑到了。那么为什么呢?假设我们现在从左上到右下已经遍历完,正在执行从右下到左上的遍历,此刻到了dp[i][j]

对于右上部分,如果该部分有0,那么到其最近的距离已经在第一次遍历时被dp[i][k], 
j < k < cols给考虑到了,而我们第二次遍历时,dp[i][j] 可能是根据它右边的值来的,
所以右上部分的0没有被遗漏;

对于左下部分,如果该部分有0,那么到其最近的距离已经在第二次遍历时被dp[p][q], 
i < p < rows, 0 <= q < cols or p = i, j < q < cols给考虑到了,
而dp[i][j] 可能是根据它下边的值来的,所以左下部分的0也没有被遗漏。
综上,只需要两次DP即可得出答案。
--------------------------
class Solution {//dp[i + 1][j + 1]表示mat[i][j]距离0的最短距离, i,j >= 0
    public int[][] updateMatrix(int[][] mat) {
        int row = mat.length;//获取数组几行几列
        int col = mat[0].length;
        int[][] dp = new int[row + 2][col + 2];//定义dp二维 : 在外围加一圈 使其和0的距离为无穷大
        for (int i = 0; i < dp.length; i++) {//初始化 左右两侧
            dp[i][0] = dp[i][col + 1] = Integer.MAX_VALUE >> 1;//! dp数组
        }
        for (int j = 0; j < dp[0].length; j++) {//初始化 上下两侧
            dp[0][j] = dp[row + 1][j] = Integer.MAX_VALUE >> 1;//dp数组
        }
        for (int i = 1; i <= row; i++) {//从左上到右下遍历一次 起始点 为 1,1
            for (int j = 1; j <= col; j++) {
                if (mat[i - 1][j - 1] == 0) dp[i][j] = 0;
                else dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;
            }//当前位置为1 距离其左上部分0的最短距离为 min(左边离0的距离, 上边离0的距离) + 1
        }
        for (int i = row; i >= 1; i--) {//从右下到左上遍历一次
            for (int j = col; j >= 1; j--) {
                if (mat[i - 1][j - 1] == 1) {//前面已经算过 mat[i][j] == 0 的情况了
                    dp[i][j] = Math.min(dp[i][j], Math.min(dp[i + 1][j], dp[i][j + 1]) + 1);
                }//当前位置为1,距离其右下部分0的最短距离为 min(右边离0的距离, 下边离0的距离) + 1
            }
        }
        int[][] res = new int[row][col];//结果集
        for (int i = 0; i < row; i++) {//取出答案
            for (int j = 0; j < col; j++) {
                res[i][j] = dp[i + 1][j + 1];
            }
        }
        return res;
    }
}

LC

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值