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;
}
}