携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情
题目
给定一个由 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]]
提示
- 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的公式: 不是 0 的元素每个方格的值是上下左右四个方向上的最小值+1, 如果自身是0那就是0.
在实现上肯定是要递推的, 但是要有一个核心点, 每个方格的感受野(CV领域中的,拿来用). 感受野: 每个元素受到哪些元素影响 当感受野是整张图时, 自身得到的值一定是全局最优值了, 也就是最终答案了. 注意 采用感受野的方式解题思想上是没问题的, 但是如果矩阵中存在障碍时采用DP的方式可能会使得感受野受阻, 此时很难通过 dp 获取全图感受野, 一般是要用DFS或是BFS了.
简单举例: 自上到下遍历
for (int i = 1; i < m; ++i) {
for (int j = 0; j < n; ++j) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j]);
}
}
此时矩阵中每个元素存的都是自身上方到自身的最小值. 他们的感受野都是各自上方的一条竖直的网格. 即他们的值是由他们感受野中所决定的最小值. 继续再从左往右遍历
for (int j = 1; j < n; ++j) {
for (int i = 0; i < m; ++i) {
dp[i][j] = Math.max(dp[i][j - 1], dp[i][j]);
}
}
此时每个元素的感受野=自身的感受野+左边元素的感受野, 逐渐向右叠加的话, 每个元素的感受野就矩阵左上角到自身的矩形. 接下来再从右到左的遍历也是一样的感受野叠加, 但是此时还有一点不一样, 之前的遍历都是前一个元素的感受野小于下一个元素, 此时是前一个元素的感受野大于自身了.
for (int j = n - 2; j >= 0; --j) {
for (int i = 0; i < m; ++i) {
dp[i][j] = Math.max(dp[i][j + 1], dp[i][j]); // dp[i][j + 1]的感受野是大于dp[i][j]的
}
}
经过此次遍历之后, 每个元素的感受野都是从自身行 到 矩阵第0行 的矩阵了 当然再经过从下向上的遍历之后, 矩阵中每个元素的感受野就是整个矩阵了, 此时矩阵中每个元素全部都存的矩阵最小值了, 就不展开了.
考虑本题, 如何遍历能让每个元素的感受野都是整张图呢?
首先从左上角开始遍历, 每个元素记录来自左边和上边的最小值+1, 或者自身是0就不变.
此时矩阵中每个元素的感受野都是从矩阵左上角到自身的一个矩阵了.
再倒过来遍历一次, 每个元素记录来自右边和下边的最小值+1, 或者自身是0就不变.
此时细致分析一下, dp[m - 1][n - 1]的元素在经过第一次遍历之后感受野就是整张图了, 所以它的左边, 上边受它的影响感受野也都是整张图了,继续迭代的话,矩阵中每个元素的感受野就都是整张图了, 所以每个元素就都得到答案了!
从这里也可以看出当我们在第二次遍历开始的时候从一个感受野为整张图的元素开始遍历就可以得到答案.
所以选取对角对矩阵进行DP就可以满足.
代码
class Solution {
public int[][] updateMatrix(int[][] mat) {
// dp
int m = mat.length, n = mat[0].length;
int[][] dp = new int[m][n];
int stepMax = 1 << 14;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (mat[i][j] != 0) dp[i][j] = stepMax;
}
}
for (int i = 0; i < m; ++i) { // 左上到右下
for (int j = 0; j < n; ++j) {
if (i != 0 && dp[i - 1][j] < dp[i][j] - 1) dp[i][j] = dp[i - 1][j] + 1;
if (j != 0 && dp[i][j - 1] < dp[i][j] - 1) 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 + 1][j] < dp[i][j] - 1) dp[i][j] = dp[i + 1][j] + 1;
if (j != n - 1 && dp[i][j + 1] < dp[i][j] - 1) dp[i][j] = dp[i][j + 1] + 1;
}
}
return dp;
}
}
结语
业精于勤,荒于嬉;行成于思,毁于随。