Skip to content

Day 44


99.岛屿数量(深搜)

注意深搜的两种写法,熟练掌握这两种写法 以及 知道区别在哪里,才算掌握的深搜。

题目链接:https://kamacoder.com/problempage.php?pid=1171

文章讲解:https://www.programmercarl.com/kamacoder/0099.岛屿的数量深搜.html

视频讲解:https://www.bilibili.com/video/BV18PRGYcEiB

思路分析

逐行搜索,如果发现当前遍历节点是陆地,则以当前起点为中心,判断四个方向是否是陆地

题解

java
import java.util.Scanner;

public class Main {
    public static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public static void dfs(int[][] grid, int x, int y, boolean[][] visited) {
        for (int i = 0; i < dir.length; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            // 检验是否越界
            if (nextY < 0 || nextX < 0 || nextX >= grid.length || nextY >= grid[0].length) {
                continue;
            }
            // 递归条件:下一个点是陆地且没有被访问过
            if(!visited[nextX][nextY] && grid[nextX][nextY] == 1){
                visited[nextX][nextY] = true;
                dfs(grid, nextX, nextY, visited);
            }
        }
    }

    public static void main(String[] args) {
        // 数据录入
        Scanner scanner = new Scanner(System.in);
        int m = scanner.nextInt();
        int n = scanner.nextInt();
        int[][] grid = new int[m][n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = scanner.nextInt();
            }
        }

        int ans = 0;
        boolean[][] visited = new boolean[m][n];

        // dfs
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 没有被访问过并且还是陆地,就以当前遍历节点开始搜索
                if (!visited[i][j] && grid[i][j] == 1) {
                    ans++;
                    visited[i][j] = true;
                    // 处理其余节点
                    dfs(grid, i, j, visited);
                }
            }
        }
        System.out.println(ans);
    }
}

99.岛屿数量(广搜)

注意广搜的两种写法,第一种写法为什么会超时, 如果自己做的录友,题目通过了,也要仔细看第一种写法的超时版本,弄清楚为什么会超时,因为你第一次 幸运 没那么想,第二次可就不一定了

题目链接:https://kamacoder.com/problempage.php?pid=1171

文章讲解:https://www.programmercarl.com/kamacoder/0099.岛屿的数量广搜.html

视频讲解:https://www.bilibili.com/video/BV12QQqYWE6x

⚠️ 注意点

入队就标记为访问过,如果是从队列中取出然后标记为访问过,就会造成同一个元素重复加入队列的情况,导致代码超时

题解

java
import java.util.*;

public class Main {

    // 定义四个方向:上、右、下、左
    public static int[][] dir = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 上、右、下、左顺序

    // 自定义一个 Pair 类来表示坐标
    static class Pair {
        int first, second;

        Pair(int first, int second) {
            this.first = first;
            this.second = second;
        }
    }

    // BFS 函数
    public static void bfs(int[][] grid, boolean[][] visited, int x, int y) {
        Queue<Pair> queue = new LinkedList<Pair>(); // 定义坐标队列
        queue.add(new Pair(x, y));
        visited[x][y] = true; // 标记已访问
        while (!queue.isEmpty()) {
            int curX = queue.peek().first;
            int curY = queue.poll().second; // 获取当前的坐标
            for (int i = 0; i < 4; i++) {
                // 顺时针遍历新的坐标
                int nextX = curX + dir[i][0];
                int nextY = curY + dir[i][1];
                // 判断是否越界
                if (nextX < 0 || nextX >= grid.length || nextY < 0 || nextY >= grid[0].length) {
                    continue;
                }
                // 如果没有访问过并且是陆地(值为 1)
                if (!visited[nextX][nextY] && grid[nextX][nextY] == 1) {
                    queue.add(new Pair(nextX, nextY)); // 将新坐标加入队列
                    visited[nextX][nextY] = true; // 标记已访问
                }
            }
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int m = sc.nextInt();
        int n = sc.nextInt();
        int[][] grid = new int[m][n];
        boolean[][] visited = new boolean[m][n];
        int ans = 0;

        // 读取 grid 的内容
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 遍历每个位置,进行 BFS 计算岛屿数量
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (!visited[i][j] && grid[i][j] == 1) {
                    ans++; // 每次找到一个未访问的陆地,岛屿数量增加
                    bfs(grid, visited, i, j); // 对该岛屿进行 BFS 遍历
                }
            }
        }

        // 输出岛屿数量
        System.out.println(ans);
    }
}

100.岛屿的最大面积

本题就是基础题了,做过上面的题目,本题很快。

题目链接:https://kamacoder.com/problempage.php?pid=1172

文章讲解:https://www.programmercarl.com/kamacoder/0100.岛屿的最大面积.html

视频讲解:https://www.bilibili.com/video/BV1FzoyY5EXH

思路分析

统计岛屿面积的变量设置为 static,这样就不会因为递归的影响重置变量的值,只要遇到岛屿就会统计,不断更新

DFS 题解

java
import java.util.Scanner;

public class Main {

    static final int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
    // 设置为 static,不会因为递归的影响重置变量的值,只要遇到岛屿就会统计,不断更新
    static int result = 0;
    static int count = 0;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] map = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map[i][j] = scanner.nextInt();
            }
        }
        boolean[][] visited = new boolean[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!visited[i][j] && map[i][j] == 1) {
                    count = 1;
                    visited[i][j] = true;
                    // 处理周围节点
                    dfs(map, visited, i, j);
                    result = Math.max(count, result);
                }
            }
        }
        System.out.println(result);
    }

    static void dfs(int[][] map, boolean[][] visited, int x, int y) {
        for (int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            // 检查越界
            if (nextX < 0 || nextY < 0 || nextX >= map.length || nextY >= map[0].length || visited[nextX][nextY] || map[nextX][nextY] == 0) {
                continue;
            }
            // 如果是陆地,且没有访问过,则进行 DFS
            if (!visited[nextX][nextY] && map[nextX][nextY] == 1) {
                visited[nextX][nextY] = true;
                count++;
                dfs(map, visited, nextX, nextY);
            }
        }
    }
}

BFS 题解

java
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
    static int result = 0;
    static int count = 0;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();

        int[][] graph = new int[n][m];

        boolean[][] visited = new boolean[n][m];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                graph[i][j] = scanner.nextInt();
            }
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!visited[i][j] && graph[i][j] == 1) {
                    // 初始面积为 1
                    count = 1;
                    // 首先处理当前节点
                    visited[i][j] = true;
                    bfs(graph, i, j, visited);
                    // 遍历完一轮,统计结果
                    result = Math.max(result, count);

                }
            }
        }
        System.out.println(result);
    }

    public static void bfs(int[][] graph, int x, int y, boolean[][] visited) {
        Queue<Pair> queue = new LinkedList<>();
        queue.add(new Pair(x, y));
        // 加入队列就设置为 true
        visited[x][y] = true;

        while (!queue.isEmpty()) {
            // 先 peek,再 poll,避免空指针异常
            int curX = queue.peek().x;
            int curY = queue.poll().y;
            for (int i = 0; i < dir.length; i++) {
                int nextX = curX + dir[i][0];
                int nextY = curY + dir[i][1];

                // 检查越界
                if (nextX < 0 || nextX >= graph.length || nextY < 0 || nextY >= graph[0].length) {
                    continue;
                }

                if (!visited[nextX][nextY] && graph[nextX][nextY] == 1) {
                    visited[nextX][nextY] = true;
                    count += 1;
                    queue.add(new Pair(nextX, nextY));
                }
            }
        }

    }

    static class Pair {
        int x;
        int y;

        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

⚠️ 更安全的写法

力扣:https://leetcode.cn/problems/max-area-of-island/

若用 DFS 题解中的思路,在力扣中可能无法通过测试用例,因为采用 static 变量,在跑多组数据时是不会被重置的,采用局部变量就可以解决这个问题

如下代码的思路实在 dfs 中统计节点,在递归中不断累加,最后返回结果

java
import java.util.Scanner;

public class Main {

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

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();
        int[][] map = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map[i][j] = scanner.nextInt();
            }
        }
        boolean[][] visited = new boolean[n][m];

        int result = 0;

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (!visited[i][j] && map[i][j] == 1) {
                    // 处理周围节点
                    int count = dfs(map, visited, i, j);
                    result = Math.max(count, result);
                }
            }
        }
        System.out.println(result);
    }

    static int dfs(int[][] map, boolean[][] visited, int x, int y) {
        int count = 1;
        visited[x][y] = true;
        for (int i = 0; i < 4; i++) {
            int nextX = x + dir[i][0];
            int nextY = y + dir[i][1];
            // 检查越界
            if (nextX < 0 || nextY < 0 || nextX >= map.length || nextY >= map[0].length || visited[nextX][nextY]
                    || map[nextX][nextY] == 0) {
                continue;
            }
            // 如果是陆地,且没有访问过,则进行 DFS
            if (!visited[nextX][nextY] && map[nextX][nextY] == 1) {
                count += dfs(map, visited, nextX, nextY);
            }
        }
        return count;
    }
}

走迷宫问题

题目链接

https://www.lanqiao.cn/problems/1216/learning/?page=1&first_category_id=1&name=迷宫

思路分析

需要考虑两个情况:(1)是障碍物(2)不是障碍物

记步的思路:新建 step 数组,step[i][j] 表示从起点到(i,j)需要走的步数

递推关系:step[nextX][nextY] = step[curX][curY] + 1

卡哥思路版本

java
import java.util.*;

public class Main {

    public static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();

        int[][] graph = new int[n][m];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                graph[i][j] = scanner.nextInt();
            }
        }

        // 注意题给的数据范围,>= 1
        int x1 = scanner.nextInt() - 1;
        int y1 = scanner.nextInt() - 1;
        int x2 = scanner.nextInt() - 1;
        int y2 = scanner.nextInt() - 1;

        Pair start = new Pair(x1, y1);
        Pair end = new Pair(x2, y2);

        boolean[][] visited = new boolean[n][m];
        // step[i][j] 表示起点到(i,j)需要走的步数
        int[][] step = new int[n][m];

        // 初始化 step 数组,所有位置的初始步数为 -1
        for (int i = 0; i < n; i++) {
            Arrays.fill(step[i], -1);
        }

        // 起点到自身需要走 0 步
        step[start.x][start.y] = 0;

        int ans = bfs(graph, start, end, visited, step);

        System.out.println(ans);
    }

    public static int bfs(int[][] graph, Pair start, Pair end, boolean[][] visited, int[][] step) {
        Queue<Pair> queue = new LinkedList<>();
        queue.add(start);
        // 加入队列即标记为访问过
        visited[start.x][start.y] = true;

        while (!queue.isEmpty()) {
            // 队列中存储的是 Pair 对象,先返回一个对象,之后再取 x,y 的值
            Pair pair = queue.poll();
            int curX = pair.x;
            int curY = pair.y;

            for (int i = 0; i < dir.length; i++) {
                int nextX = curX + dir[i][0];
                int nextY = curY + dir[i][1];

                // 检查越界
                if (nextX < 0 || nextX >= graph.length || nextY < 0 || nextY >= graph[0].length) {
                    continue;
                }

                // 到达终点的判断,前提是可以走
                if (nextX == end.x && nextY == end.y && graph[nextX][nextY] != 0) {
                    return step[curX][curY] + 1;
                }

                if (!visited[nextX][nextY] && graph[nextX][nextY] == 1) {
                    queue.add(new Pair(nextX, nextY));
                    visited[nextX][nextY] = true;
                    // 下一个节点的步数 = 上一个节点距离源点所需的步数 + 1
                    step[nextX][nextY] = step[curX][curY] + 1;
                }
            }
        }

        // 终点不可达
        return -1;
    }

    static class Pair {
        int x;
        int y;

        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}

continue 版本

先 continue 所有不符合条件的情况,之后都是符合条件的,继续操作即可,这种写法不容易漏情况

(1)是障碍物且访问过

(2)越界情况

java
import java.util.*;

public class Main {

    public static int[][] dirs = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int m = scanner.nextInt();

        int[][] graph = new int[n][m];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                graph[i][j] = scanner.nextInt();
            }
        }

        // 注意题给的数据范围,>= 1
        int x1 = scanner.nextInt() - 1;
        int y1 = scanner.nextInt() - 1;
        int x2 = scanner.nextInt() - 1;
        int y2 = scanner.nextInt() - 1;

        Pair start = new Pair(x1, y1);
        Pair end = new Pair(x2, y2);

        boolean[][] visited = new boolean[n][m];
        // step[i][j] 表示起点到(i,j)需要走的步数
        int[][] step = new int[n][m];

        // 初始化 step 数组,所有位置的初始步数为 -1
        for (int i = 0; i < n; i++) {
            Arrays.fill(step[i], -1);
        }

        // 起点到自身需要走 0 步
        step[start.x][start.y] = 0;

        int ans = bfs(graph, start, end, visited, step);

        System.out.println(ans);
    }

    public static int bfs(int[][] graph, Pair start, Pair end, boolean[][] visited, int[][] step) {
        Queue<Pair> queue = new LinkedList<>();
        queue.add(start);
        // 加入队列即标记为访问过
        visited[start.x][start.y] = true;

        while (!queue.isEmpty()) {
            // 队列中存储的是 Pair 对象,先返回一个对象,之后再取 x,y 的值
            Pair pair = queue.poll();
            int curX = pair.x;
            int curY = pair.y;

            for (int i = 0; i < dirs.length; i++) {
                int nextX = curX + dirs[i][0];
                int nextY = curY + dirs[i][1];

                // 检查越界
                if (nextX < 0 || nextX >= graph.length || nextY < 0 || nextY >= graph[0].length) {
                    continue;
                }

                // 如果已经访问过,且是障碍物
                if (visited[nextX][nextY] || graph[nextX][nextY] == 0) {
                    continue;
                }

                // 到达终点,返回结果
                if (nextX == end.x && nextY == end.y) {
                    return step[curX][curY] + 1;
                }

                // 更新步数,并加入队列
                step[nextX][nextY] = step[curX][curY] + 1;
                visited[nextX][nextY] = true;  // 标记为已访问
                queue.add(new Pair(nextX, nextY));  // 加入队列
            }
        }

        // 终点不可达
        return -1;
    }

    static class Pair {
        int x;
        int y;

        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
    }
}