542.给定一个由 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]]
- 比较容易想到的是层序遍历,遍历每个点,每一轮遍历都将该点作为起点得到与其最近的 0 的距离。如果本身就等于 0 直接返回 0,否则遍历到第几层就是距离多少。
- 但是这样真的很浪费时间,因为每一个点都要从自身出发寻找一遍离他最近的 0。我们不妨转换一下思路,直接从 0 开始往外找,第一次层序遍历时 0 四周的值都为 1,然后继续层序遍历得到再外面一层的所有的 2…
- 借用一下官方的演示过程,下划线表示 1.
-
_ _ _ _ _ 1 _ _ 2 1 2 _ 2 1 2 3 _ 0 _ _ ==> 1 0 1 _ ==> 1 0 1 2 ==> 1 0 1 2 _ _ 0 _ _ 1 0 1 2 1 0 1 2 1 0 1 _ _ _ _ _ _ 1 _ _ 2 1 2 3 2 1 2
-
int m,n; int[][] ans; int[] dx = new int[]{0,0,-1,1}; int[] dy = new int[]{-1,1,0,0}; boolean[][] visited; public int[][] updateMatrix(int[][] mat) { m = mat.length; n = mat[0].length; ans = new int[m][n]; visited = new boolean[m][n]; Queue<int[]> queue = new LinkedList<>(); for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ if(mat[i][j] == 0){ queue.add(new int[]{i,j}); } } } bfs(mat,queue); return ans; } public void bfs(int[][] mat,Queue<int[]> queue){ while(!queue.isEmpty()){ int size = queue.size(); while(size-- > 0){ int[] cell = queue.poll(); int ix = cell[0]; int iy = cell[1]; for(int i=0;i<4;i++){ int x = ix+dx[i]; int y = iy+dy[i]; if(x < 0 || x >= m || y < 0 || y >= n || visited[x][y]){ continue; } ans[x][y] = mat[x][y] == 0? 0 : ans[ix][iy]+1; queue.add(new int[]{x,y}); visited[x][y] = true; } } } }
- 用 dp 也可以。设 f(x,y) 为坐标 [x,y] 到 0 的最近距离。首先毋庸置疑的一点就是,一个为 1 的点,在他的左上,左下,右上,右下四个部分,总有一部分是包含了 0 的。如果最近的 0 在左上部分,那么我可能是从当前的 [x,y] 往左一步再加上左边的点到 0 最近距离 f(x,y-1);也可能是先往上走一步再加上上边的点到 0 最近距离 f(x-1,y),我们取最小值两者的。也就是说
f(x,y) = Math.min(f(x-1,y),f(x,y-1)) + 1
,这是最近的 0 在 [x,y] 左上部分的情况,我们把四种情况都试一次,试的过程中每次都取最小值,得到的最终结果就是我们要的答案。 -
int max = Integer.MAX_VALUE; public int[][] updateMatrix(int[][] mat) { int m = mat.length, n = mat[0].length; int[][] dp = new int[m][n]; // 赋初始值,max/2 是怕 max+1 超过 int 最大值 for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ dp[i][j] = mat[i][j] == 0? 0 : max / 2; } } // 0 在左上 // 因为我们正确的结果肯定是从正确的结果递推得来的 // 也就是说如果 0 在左上部分,从左上的 0 开始递推才能得到正确的结果 // 所以 [i,j] 从 [0,0] 开始 for(int i = 0; i < m; i++){ for(int j = 0; j < n; j++){ // 往上一步再加上 f(x-1,y) if(i - 1 >= 0){ dp[i][j] = Math.min(dp[i][j], dp[i-1][j] + 1); } // 往左一步再加上 f(x,y-1) if(j - 1 >= 0){ dp[i][j] = Math.min(dp[i][j], dp[i][j-1] + 1); } } } // 0 在左下 // 所以从左下角 [m-1,0] 开始递推 // 之后同理 for(int i = m - 1; i >= 0; i--){ for(int j = 0; j < n; j++){ if(i + 1 < m){ dp[i][j] = Math.min(dp[i][j], dp[i+1][j] + 1); } if(j - 1 >= 0){ dp[i][j] = Math.min(dp[i][j], dp[i][j-1] + 1); } } } //右上 for(int i = 0; i < m; i++){ for(int j = n - 1; j >= 0; j--){ if(i - 1 >= 0){ dp[i][j] = Math.min(dp[i][j], dp[i-1][j] + 1); } if(j + 1 < n){ dp[i][j] = Math.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 + 1 < m){ dp[i][j] = Math.min(dp[i][j], dp[i+1][j] + 1); } if(j + 1 < n){ dp[i][j] = Math.min(dp[i][j], dp[i][j+1] + 1); } } } return dp; }
- 我们还能优化一下,当我们递推出来了比如左下部分正确结果,那么我们从左下往右上递推的时候实际上就能推出全部的正确结果了。因为一个矩阵,你得到了左下角的正确答案,往右上递推的时候,左下角的右上部分不就等于全部吗?所以你可以只保留左上和右下,或者右上左下的递推过程即可。