目录

网格图-Day09-网格图BFS-542.-01-矩阵,1765.-地图中的最高点,994.-腐烂的橘子

网格图–Day09–网格图BFS–542. 01 矩阵,1765. 地图中的最高点,994. 腐烂的橘子

网格图–Day09–网格图BFS–542. 01 矩阵,1765. 地图中的最高点,994. 腐烂的橘子

今天要训练的题目类型是:【网格图BFS】,题单来自@ 。

适用于需要计算最短距离(最短路)的题目。

BFS的时候,最需要注意的三个点:

  • step/层数什么时候加一?进去就加,还是出来再加?
  • 处理节点的时候,是处理cur?还是处理cur的周边节点?
  • 特别要注意:先标记,再入队!不然会超时!

今天的题目特点:

都是先找到第一层,标记,然后一层一层BFS就行了,同一个套路。

今天复杂的地方在于:找到可用的染色标记,一般0或者1原意都会是水陆,意义被占用。要么就是先标记成-1意为未访问。要么就直接使用visited数组。推荐使用visited数组,这样不容易出错。

思路:

  • 先找到第一层靠近水的1
    • for循环寻找所有1
      • 把所有的1标记成-1,意为“未访问”
      • 如果这个1靠近水(4个方向有一个为0),就染色为1,并入队。
  • 开始一层一层遍历(染色标记从2开始)
    • 遍历cur周边的四个方向
      • 索引合法,且未访问过。就先染色(标记),再进队。
class Solution {

    private static final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

    public int[][] updateMatrix(int[][] mat) {

        int n = mat.length;
        int m = mat[0].length;
        Deque<int[]> que = new ArrayDeque<>();

        // 先找到第一层靠近水的1
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (mat[i][j] == 1) {
                    // 把所有的1标记成-1,意为“未访问”
                    mat[i][j] = -1;
                    boolean nearWater = false;
                    for (int[] d : DIRS) {
                        int x = i + d[0];
                        int y = j + d[1];
                        if (x >= 0 && x < n && y >= 0 && y < m && mat[x][y] == 0) {
                            nearWater = true;
                            break;
                        }
                    }
                    if (nearWater) {
                        // 先染色(标记),再进队
                        mat[i][j] = 1;
                        que.offer(new int[] { i, j });
                    }
                }
            }
        }

        // 开始一层一层遍历
        // 染色标记从2开始
        int step = 2;
        while (!que.isEmpty()) {
            int size = que.size();
            while (size-- > 0) {
                int[] cur = que.poll();
                int i = cur[0];
                int j = cur[1];
                for (int[] d : DIRS) {
                    int x = cur[0] + d[0];
                    int y = cur[1] + d[1];
                    // 索引合法,且未访问过
                    if (x >= 0 && x < n && y >= 0 && y < m && mat[x][y] == -1) {
                        // 先染色(标记),再进队
                        mat[x][y] = step;
                        que.offer(new int[] { x, y });
                    }
                }
            }
            step++;
        }
        return mat;
    }
}

思路:

题目提示说,和542题是一样的,搞得我兴高采烈的复制代码过去,发现是过不了的。其实两题是有区别的,但是总体思路是一样的——找到第一层,标记。然后一层一层BFS。

不同点在于:

  • 第一次遍历全矩阵的时候
    • 找到海水就入队,并标记高度为0
    • 找到陆地,就标记为-1,未访问
  • 开始一层一层遍历(染色标记从1开始)
    • 遍历cur周边的四个方向
      • 索引合法,且未访问过。就先染色(标记),再进队。
class Solution {

    private static final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

    public int[][] highestPeak(int[][] isWater) {
        int[][] mat = isWater;
        int n = mat.length;
        int m = mat[0].length;
        Deque<int[]> que = new ArrayDeque<>();
        boolean[][] visited = new boolean[n][m];

        // 先找到第0层
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (mat[i][j] == 1) {
                    // 海水(1)的高度为0
                    // 先标记,再入队
                    mat[i][j] = 0;
                    que.offer(new int[] { i, j });
                } else {
                    // 0的定义已经被占用,未访问的陆地,标记为-1
                    mat[i][j] = -1;
                }
            }
        }

        // 开始一层一层遍历
        // 染色标记从1开始
        int step = 1;
        while (!que.isEmpty()) {
            int size = que.size();
            while (size-- > 0) {
                int[] cur = que.poll();
                for (int[] d : DIRS) {
                    int x = cur[0] + d[0];
                    int y = cur[1] + d[1];
                    // 索引合法,且未访问过
                    if (x >= 0 && x < n && y >= 0 && y < m && mat[x][y] == -1) {
                        // 先标记,再入队
                        mat[x][y] = step;
                        que.offer(new int[] { x, y });
                    }
                }
            }
            step++;
        }
        return mat;
    }
}

思路:

这道题目是HOT100里面的。用心做。

统计BFS次数,如果BFS之后,如果还有1,那么就是-1.

  1. 收集0分钟时候的烂橘子,第一层
  2. 开始BFS
    • 这一层要遍历的节点为队列中的前size个(因为后面可能会加新节点到que)
    • 出队一个,cur是烂橘子,污染它周边的4个
    • 当索引合法,且是好橘子。污染它,并入队,下一层再处理
    • 遍历完这一层,判断que.size()是否大于0,如果还有下一层,还需要多1分钟污染,此时minute++
  3. BFS完了以后,检查是否还有好橘子,有则返回-1.无则返回minute
class Solution {
    // 统计BFS次数,如果BFS之后,如果还有1,那么就是-1.

    private static final int[][] DIRS = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };

    public int orangesRotting(int[][] grid) {
        int res = 0;
        Queue<int[]> que = new ArrayDeque<>();
        int n = grid.length;
        int m = grid[0].length;

        // 收集0分钟时候的烂橘子,第一层
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == 2) {
                    que.offer(new int[] { i, j });
                }
            }
        }

        // 开始BFS
        while (!que.isEmpty()) {

            // 这一层要遍历的节点为队列中的前size个(因为后面可能会加新节点到que)
            int size = que.size();
            while (size-- > 0) {

                // cur是烂橘子,污染它周边的4个
                int[] cur = que.poll();
                for (int k = 0; k < DIRS.length; k++) {
                    int x = cur[0] + DIRS[k][0];
                    int y = cur[1] + DIRS[k][1];

                    // 索引合法,且是好橘子
                    if (x >= 0 && x < n && y >= 0 && y < m && grid[x][y] == 1) {
                        // 污染它,并入队,下一层处理
                        grid[x][y] = 2;
                        que.offer(new int[] { x, y });
                    }
                }
            }
            // 如果还有下一层,还需要多1分钟污染
            if (que.size() > 0) {
                res++;
            }
        }

        // 检查有无没污染的橘子,有的话返回-1
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == 1) {
                    return -1;
                }
            }
        }
        return res;
    }
}