• 【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;
      }
    }
    
  • 相关阅读:
    MySql数据库的导入_命令工具
    Java_JVM学习笔记(深入理解Java虚拟机)___重点
    HTML——<meta http-equiv="content-type" content="text/html; charset=UTF-8">
    HTML——表格table标签,tr或者td
    Java _Map接口的使用(转载)
    Java_Web _Servlet生命周期实验
    Java_Web___字符串转码String.getBytes()和new String()——(转)
    Java_Iterator-------迭代器配合Listarray使用,具有更多的功能(转载)
    Java_LIST使用方法和四种遍历arrayList方法
    Java_web 乱码和一些地址输错的问题(原创)
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13307956.html
Copyright © 2020-2023  润新知