day50 代码随想录算法训练营 图论专题3

1 今日打卡

孤岛的总面积 101. 孤岛的总面积

沉没孤岛 102. 沉没孤岛

高山流水 103. 高山流水

建造最大岛屿 104. 建造最大岛屿

2 孤岛的总面积

2.1 思路

方向数组:dir 定义了上下左右四个移动方向,是网格类 DFS/BFS 的常用写法,避免重复写四个方向的判断逻辑。

DFS 函数:

核心作用是 "淹没" 当前陆地及所有相连的陆地(置为 0);

先标记当前位置为 0,再递归处理四个方向的相邻陆地,确保所有相连的边界陆地都被消除。

边界处理:

遍历网格的四条边界(上、下、左、右),只要边界上有陆地(1),就通过 DFS 消除其所有相连的陆地;

这一步的目的是排除所有 "非孤岛"(与外界连通的陆地)。

统计结果:

边界相连的陆地都被置为 0 后,剩余的 1 就是完全被水域包围的孤岛;

统计剩余 1 的数量,即为孤岛的总面积。

注意:不需要visited数组,因为常规的 visited[x][y] = true 是 "额外记录" 该位置已处理;这段代码中 map[x][y] = 0 是 "直接修改原数据" 来标记该位置已处理(因为原本的 1 代表陆地,置 0 后就不再是陆地,后续遍历会自动跳过)。

2.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 定义上下左右四个方向的偏移量(dx, dy)
    private static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    /**
     * 深度优先搜索(DFS):将当前位置及相连的1全部置为0(消除非孤岛)
     * @param map 二维网格
     * @param x 当前位置行坐标
     * @param y 当前位置列坐标
     */
    public static void dfs(int[][] map, int x, int y) {
        // 标记当前位置为0(已处理)
        map[x][y] = 0;
        // 遍历四个方向
        for (int i = 0; i < 4; i++) {
            // 计算下一个位置的坐标
            int nextX = x + dir[i][0], nextY = y + dir[i][1];
            // 边界检查:超出网格范围则跳过
            if (nextX < 0 || nextY < 0 || nextX >= map.length || nextY >= map[0].length) continue;
            // 如果下一个位置是0(已处理/非陆地),则跳过
            if (map[nextX][nextY] == 0) continue;
            // 递归处理下一个位置
            dfs(map, nextX, nextY);
        }
        return;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入网格的行数n和列数m
        int n = sc.nextInt(), m = sc.nextInt();
        // 初始化n行m列的二维网格
        int[][] map = new int[n][m];
        // 输入网格数据(1表示陆地,0表示水域)
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map[i][j] = sc.nextInt();
            }
        }

        // 第一步:清除所有与边界相连的陆地(非孤岛)
        // 处理左右两列边界(第0列和第m-1列)
        for (int i = 0; i < n; i++) {
            // 如果左边界当前位置是1,执行DFS消除相连陆地
            if (map[i][0] == 1) {
                dfs(map, i, 0);
            }
            // 如果右边界当前位置是1,执行DFS消除相连陆地
            if (map[i][m - 1] == 1) {
                dfs(map, i, m - 1);
            }
        }
        // 处理上下两行边界(第0行和第n-1行)
        for (int j = 0; j < m; j++) {
            // 如果上边界当前位置是1,执行DFS消除相连陆地
            if (map[0][j] == 1) {
                dfs(map, 0, j);
            }
            // 如果下边界当前位置是1,执行DFS消除相连陆地
            if (map[n - 1][j] == 1) {
                dfs(map, n - 1, j);
            }
        }

        // 第二步:统计剩余的1的数量(即孤岛的总面积)
        int count = 0;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (map[i][j] == 1) {
                    ++count;
                }
            }
        }
        // 输出孤岛总面积
        System.out.println(count);
        
        sc.close(); // 关闭扫描器(规范写法)
    }
}

3 沉没孤岛

3.1 思路

跟孤岛总面积同样的思路

核心思路:先标记、后还原------ 用 2 标记所有与边界相连的陆地,最后只保留这些陆地,其余(孤岛)全部置 0;

和 "统计孤岛面积" 的同源性:都是从边界出发做 DFS,区别仅在于 "标记方式"(置 0 vs 置 2)和 "最终处理逻辑";

关键细节:用 2 作为标记值,是为了和原始的 1(陆地)、0(水域)区分,避免修改过程中混淆状态。

3.2 实现代码

java 复制代码
import java.util.*;
public class Main{
    // 定义上下左右四个方向的偏移量
    public static int[][] dir = {{0,1}, {0, -1}, {1, 0}, {-1,0}};

    /**
     * DFS核心功能:标记与边界相连的陆地(置为2)
     * @param map 二维网格(1=陆地,0=水域,2=标记的边界陆地)
     * @param x 当前位置行坐标
     * @param y 当前位置列坐标
     */
    public static void dfs(int[][] map, int x, int y) {
        // 将当前边界陆地标记为2(区别于原始的1和0)
        map[x][y] = 2;
        // 遍历四个方向
        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) continue;
            // 跳过已标记(2)或水域(0)的位置,只处理未标记的陆地(1)
            if(map[nextX][nextY] == 2 || map[nextX][nextY] == 0) continue;
            // 递归标记相连的陆地
            dfs(map, nextX, nextY);
        }
        return;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入网格行数n、列数m
        int n = sc.nextInt(), m = sc.nextInt();
        // 初始化n行m列的网格
        int[][] map = new int[n][m];
        // 输入网格数据(1=陆地,0=水域)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                map[i][j] = sc.nextInt();
            }
        }

        // 第一步:标记所有与边界相连的陆地
        // 处理左右边界(第0列、第m-1列)
        for(int i = 0; i < n; i++) {
            if(map[i][0] == 1) dfs(map, i ,0);      // 左边界有陆地则标记
            if(map[i][m-1] == 1) dfs(map, i, m-1);  // 右边界有陆地则标记
        }
        // 处理上下边界(第0行、第n-1行)
        for(int j = 0; j < m; j++) {
            if(map[0][j] == 1) dfs(map, 0, j);      // 上边界有陆地则标记
            if(map[n-1][j] == 1) dfs(map, n-1, j);  // 下边界有陆地则标记
        }

        // 第二步:沉没孤岛(还原标记的陆地为1,其余置0)
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j ++) {
                if(map[i][j] == 2) {
                    // 标记过的陆地(与边界相连)保留为1
                    System.out.print(1 + " ");
                }else{
                    // 未标记的陆地(孤岛)或水域,全部置0
                    System.out.print(0 +" ");
                }
            }
            // 每行输出完换行
            System.out.print("\n");
        }
    }
}

4 高山流水

4.1 思路

核心功能是找出 "高山流水" 中的关键位置------ 即从该位置出发的水流,既能流到地图的左上边界(第一行 / 第一列),又能流到右下边界(最后一行 / 最后一列)的位置。其核心逻辑是:

对每个位置执行 DFS,标记所有 "水流可达" 的位置(水流规则:只能从高 / 相等处流向低 / 相等处);

检查 DFS 标记的区域是否同时覆盖左上边界和右下边界;

若同时覆盖,则该位置是目标位置,输出其坐标。

4.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 定义上下左右四个水流方向的偏移量
    private static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入地图的行数n和列数m
        int n = sc.nextInt(), m = sc.nextInt();
        // 初始化n行m列的高度地图(值越大表示地势越高)
        int[][] map = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                map[i][j] = sc.nextInt(); // 输入每个位置的高度
            }
        }

        // 遍历地图上的每一个位置,判断是否为目标位置
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 若当前位置满足"水流能到左上+右下边界",输出坐标
                if (isResult(map, i, j)) {
                    System.out.println(i + " " + j);
                }
            }
        }
        sc.close();
    }

    /**
     * DFS:标记从(x,y)出发,水流可达的所有位置
     * 水流规则:只能从当前位置流向"高度≤当前高度"的相邻位置(水往低处流)
     * @param visited 标记可达位置的数组
     * @param map 高度地图
     * @param x 当前位置行坐标
     * @param y 当前位置列坐标
     */
    public static void dfs(boolean[][] visited, int[][] map, int x, int y) {
        // 若当前位置已访问过,直接返回(避免重复递归)
        if (visited[x][y]) return;
        // 标记当前位置为"可达"
        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) continue;
            // 水流规则检查:若下一个位置高度 > 当前位置,水流无法到达,跳过
            if (map[x][y] < map[nextX][nextY]) continue;

            // 递归处理下一个可达位置
            dfs(visited, map, nextX, nextY);
        }
        return;
    }

    /**
     * 判断从(x,y)出发的水流是否能同时到达左上边界和右下边界
     * @param map 高度地图
     * @param x 起始位置行坐标
     * @param y 起始位置列坐标
     * @return 满足条件返回true,否则返回false
     */
    public static boolean isResult(int[][] map, int x, int y) {
        int n = map.length;    // 地图行数
        int m = map[0].length; // 地图列数

        // 初始化访问标记数组(每次判断一个位置都重新初始化,避免干扰)
        boolean[][] visited = new boolean[n][m];
        // 执行DFS,标记从(x,y)出发所有水流可达的位置
        dfs(visited, map, x, y);

        // 标记1:是否能到达左上边界(第一行 或 第一列)
        boolean isFirst = false;
        // 检查第一行是否有可达位置
        for (int i = 0; i < m; i++) {
            if (visited[0][i]) {
                isFirst = true;
                break;
            }
        }
        // 若第一行无可达位置,检查第一列
        if (!isFirst) {
            for (int i = 0; i < n; i++) {
                if (visited[i][0]) {
                    isFirst = true;
                    break;
                }
            }
        }

        // 标记2:是否能到达右下边界(最后一列 或 最后一行)
        boolean isSecond = false;
        // 检查最后一列是否有可达位置
        for (int i = 0; i < n; i++) {
            if (visited[i][m - 1]) {
                isSecond = true;
                break;
            }
        }
        // 若最后一列无可达位置,检查最后一行
        if (!isSecond) {
            for (int i = 0; i < m; i++) {
                if (visited[n - 1][i]) {
                    isSecond = true;
                    break;
                }
            }
        }

        // 同时到达左上和右下边界,返回true
        return isFirst && isSecond;
    }
}

5 建造最大岛屿

5.1 思路

先计算原始矩阵的最大岛屿面积(作为初始最大值);

遍历矩阵中每一个值为 0 的位置:

临时将该位置改为 1;

计算此时矩阵的最大岛屿面积;

改回 0(避免影响下一次计算);

更新全局最大面积;

处理特殊情况(全 1 矩阵→直接返回总大小;全 0 矩阵→返回 1)。

5.2 实现代码

java 复制代码
import java.util.*;

public class Main {
    // 上下左右四个方向
    private static int[][] dir = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

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

        // 1. 输入矩阵
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                grid[i][j] = sc.nextInt();
            }
        }

        // 2. 初始最大面积:原始矩阵的最大岛屿面积
        int maxArea = calculateMaxArea(grid);

        // 3. 暴力枚举每个0的位置,填1后计算最大面积
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (grid[i][j] == 0) {
                    // 临时填1
                    grid[i][j] = 1;
                    // 计算填1后的最大面积
                    int currentMax = calculateMaxArea(grid);
                    // 更新全局最大面积
                    maxArea = Math.max(maxArea, currentMax);
                    // 改回0,不影响下一次枚举
                    grid[i][j] = 0;
                }
            }
        }

        // 4. 输出结果(全0矩阵时,maxArea会是1)
        System.out.println(maxArea);
        sc.close();
    }

    /**
     * 辅助方法:计算当前矩阵的最大岛屿面积
     * @param grid 矩阵
     * @return 最大岛屿面积
     */
    private static int calculateMaxArea(int[][] grid) {
        int n = grid.length;
        int m = grid[0].length;
        // 标记已访问的位置(避免重复计算同一个岛屿)
        boolean[][] visited = new boolean[n][m];
        int currentMax = 0;

        // 遍历所有位置,计算每个岛屿的面积
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                // 未访问的陆地,计算面积
                if (grid[i][j] == 1 && !visited[i][j]) {
                    int area = dfs(grid, visited, i, j);
                    currentMax = Math.max(currentMax, area);
                }
            }
        }
        return currentMax;
    }

    /**
     * DFS:计算单个岛屿的面积(带visited标记,避免修改原矩阵)
     * @param grid 矩阵
     * @param visited 访问标记数组
     * @param x 当前行
     * @param y 当前列
     * @return 该岛屿的面积
     */
    private static int dfs(int[][] grid, boolean[][] visited, int x, int y) {
        // 边界检查 + 不是陆地 + 已访问 → 返回0
        if (x < 0 || x >= grid.length || y < 0 || y >= grid[0].length 
                || grid[x][y] != 1 || visited[x][y]) {
            return 0;
        }

        // 标记已访问
        visited[x][y] = true;
        int area = 1; // 当前格面积为1

        // 遍历四个方向,累加面积
        for (int[] d : dir) {
            area += dfs(grid, visited, x + d[0], y + d[1]);
        }
        return area;
    }
}
相关推荐
维齐洛波奇特利(male)2 小时前
IDEA 实例类多开bug:勾选后还是只能运行一个类
java·bug·intellij-idea
蜜獾云2 小时前
设计模式之简单工厂模式(4):创建对象时不会暴露创建逻辑
java·设计模式·简单工厂模式
Flying pigs~~2 小时前
我的leetcode hot100之行(持续更新)
数据结构·算法·leetcode
滴滴答滴答答2 小时前
机考刷题之 7 LeetCode 240 搜索二位矩阵Ⅱ
java·算法·leetcode
MX_93592 小时前
Spring的xml方式声明式事务控制
xml·java·后端·spring
武帝为此2 小时前
【HMAC加密算法介绍】
算法·密码学
进击的荆棘2 小时前
优选算法——模拟
java·开发语言·算法·模拟
仰泳的熊猫2 小时前
题目2086:蓝桥杯算法提高VIP-最长公共子序列
数据结构·c++·算法·蓝桥杯·动态规划
请你喝好果汁6412 小时前
ML-线性回归(Linear Regression)
算法·回归·线性回归