【LeetCode】542.01矩阵(Bfs+动态规划,java实现)

题目

链接

image-20200715201915082

题解

一、广度优先搜索

思路:

  • 对于 「Tree 的 BFS」 (典型的「单源 BFS」) 大家都已经轻车熟路了:
    • 首先把 root 节点入队,再一层一层无脑遍历就行了。
  • 对于 「图 的 BFS」 (「多源 BFS」) 做法其实也是一样滴~,与 「Tree 的 BFS」的区别注意以下两条就 ok 辣~
    • Tree 只有 1 个 root,而图可以有多个源点,所以首先需要把多个源点都入队;
    • Tree 是有向的因此不需要标识是否访问过,而对于无向图来说,必须得标志是否访问过哦!并且为了防止某个节点多次入队,需要在其入队之前就将其设置成已访问!【 看见很多人说自己的 BFS 超时了,坑就在这里哈哈哈

做法:

根据上述思路,本题怎么做就很简单了:

  • 首先把每个源点 0入队,然后从各个 0 同时开始一圈一圈的向 11 扩散(每个 11 都是被离它最近的 00 扩散到的 ),扩散的时候可以设置 int[][] dist 来记录距离(即扩散的层次)并同时标志是否访问过。对于本题是可以直接修改原数组 int[][] matrix 来记录距离和标志是否访问的,这里要注意先把 matrix 数组中 1 的位置设置成 -1 (设成Integer.MAX_VALUE啦,m * n啦,10000啦都行,只要是个无效的距离值来标志这个位置的 1 没有被访问过就行辣~)

复杂度分析:

  • 每个点入队出队一次,所以时间复杂度:O(n * m)
  • 虽然我们是直接原地修改的原输入数组来存储结果,但最差的情况下即全都是 00 时,需要把 m * n个 0都入队,因此空间复杂度是 O(n * m)
class Solution {
    public int[][] updateMatrix(int[][] matrix) {
        // 首先将所有的 0 都入队,并且将 1 的位置设置成 -1,表示该位置是 未被访问过的 1
        Queue<int[]> queue = new LinkedList<>();
        int m = matrix.length, n = matrix[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    queue.offer(new int[] {i, j});
                } else {
                    matrix[i][j] = -1;
                } 
            }
        }
        
        int[] dx = new int[] {-1, 1, 0, 0};
        int[] dy = new int[] {0, 0, -1, 1};
        while (!queue.isEmpty()) {
            int[] point = queue.poll();
            int x = point[0], y = point[1];
            for (int i = 0; i < 4; i++) {
                int newX = x + dx[i];
                int newY = y + dy[i];
                // 如果四邻域的点是 -1,表示这个点是未被访问过的 1
                // 所以这个点到 0 的距离就可以更新成 matrix[x][y] + 1。
                if (newX >= 0 && newX < m && newY >= 0 && newY < n 
                        && matrix[newX][newY] == -1) {
                    matrix[newX][newY] = matrix[x][y] + 1;
                    queue.offer(new int[] {newX, newY});
                }
            }
        }

        return matrix;
    }
}

另一种 BFS 思路:

本题还有一种 BFS 的做法,就是先找出在 00 边上的所有的 11,然后把这些 11 放到队列里,后续BFS的时候就只关心 11 的值。
直接看注释叭~~

class Solution {
    public int[][] updateMatrix(int[][] matrix) {
        // 首先将 0 边上的 1 入队
        int[] dx = new int[] {-1, 1, 0, 0};
        int[] dy = new int[] {0, 0, -1, 1};
        Queue<int[]> queue = new LinkedList<>();
        int m = matrix.length, n = matrix[0].length;
        int[][] res = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    for (int k = 0; k < 4; k++) {
                        int x = i + dx[k];
                        int y = j + dy[k];
                        if (x >= 0 && x < m && y >= 0 && y < n 
                                && matrix[x][y] == 1 && res[x][y] == 0) {
                            // 这是在 0 边上的1。需要加上 res[x][y] == 0 的判断防止重复入队
                            res[x][y] = 1;
                            queue.offer(new int[] {x, y});
                        }
                    }
                }
            }
        }

        while (!queue.isEmpty()) {
            int[] point = queue.poll();
            int x = point[0], y = point[1];
            for (int i = 0; i < 4; i++) {
                int newX = x + dx[i];
                int newY = y + dy[i];
                if (newX >= 0 && newX < m && newY >= 0 && newY < n 
                        && matrix[newX][newY] == 1 && res[newX][newY] == 0) {
                    res[newX][newY] = res[x][y] + 1;
                    queue.offer(new int[] {newX, newY});
                }
            }
        }

        return res;
    }
}

二、动态规划

对于任一点 (i, j)(i,j),距离 0 的距离为:

f(i,j)={1+min(f(i1,j),f(i,j1),f(i+1,j),f(i,j+1))if matrix[i][j] == 10if matrix[i][j] == 0f(i, j) = egin{cases} 1 + min(f(i-1, j), f(i, j-1), f(i+1, j), f(i, j+1)) & ext{if matrix[i][j] == 1} \ 0 & ext{if matrix[i][j] == 0} end{cases}

因此我们用 $dp[i][j] $来表示该位置距离最近的 0 的距离。
我们发现 $dp[i][j] $ 是由其上下左右四个状态来决定,无法从一个方向开始递推!

于是我们尝试将问题分解:

  1. 距离 (i, j)(i,j) 最近的 0 的位置,是在其 「左上,右上,左下,右下」4个方向之一;

  2. 因此我们分别从四个角开始递推,就分别得到了位于「左上方、右上方、左下方、右下方」距离 (i, j)(i,j) 的最近的 0 的距离,取 min 即可;

  3. 通过上两步思路,我们可以很容易的写出 4 个双重 for 循环,动态规划的解法写到这一步其实已经完全 OK了;

  4. 如果第三步还不满足的话,从四个角开始的4次递推,其实还可以优化成从任一组对角开始的 2次递推,比如只写从左上角、右下角开始递推就行了,为啥这样可以呢?且听我不负责任的草率论证 = = #

    • 首先从左上角开始递推 $dp[i][j] $是由其 「左方」和 「左上方」的最优子状态决定的;
    • 然后从右下角开始递推$dp[i][j] $是由其 「右方」和 「右下方」的最优子状态决定的;
    • 看起来第一次递推的时候,把「右上方」的最优子状态给漏掉了,其实不是的,因为第二次递推的时候「右方」的状态在第一次递推时已经包含了「右上方」的最优子状态了;
    • 看起来第二次递推的时候,把「左下方」的最优子状态给漏掉了,其实不是的,因为第二次递推的时候「右下方」的状态在第一次递推时已经包含了「左下方」的最优子状态了。
class Solution {
  public int[][] updateMatrix(int[][] matrix) {
    int m = matrix.length, n = matrix[0].length;
    int[][] dp = new int[m][n];
    for (int i = 0; i < m; i++) {
      for (int j = 0; j < n; j++) {
        dp[i][j] = matrix[i][j] == 0 ? 0 : 10000;
      }
    }

    // 从左上角开始
    for (int i = 0; i < m; i++) {
      for (int j = 0; j < n; j++) {
        if (i - 1 >= 0) {
          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 = 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;
  }
}
原文地址:https://www.cnblogs.com/hzcya1995/p/13307956.html