voiddfs(int[][] grid, int i, int j, boolean[] visited){ int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return;
} if (visited[i][j]) { // 已遍历过 (i, j) return;
}
// 进入节点 (i, j)
visited[i][j] = true; // 递归遍历上下左右的节点 for (int[] d : dirs) { int next_i = i d[0]; int next_j = j d[1];
dfs(grid, next_i, next_j);
} // 离开节点 (i, j)
// visited[i][j] = true;
}
这种写法无非就是用 for 循环处理上下左右的遍历罢了,你可以按照个人喜好选择写法。
// 主函数,计算岛屿数量 intnumIslands(char[][] grid){ int res = 0; int m = grid.length, n = grid[0].length; // 遍历 grid for (int i = 0; i < m; i ) { for (int j = 0; j < n; j ) { if (grid[i][j] == '1') { // 每发现一个岛屿,岛屿数量加一
res ; // 然后使用 DFS 将岛屿淹了
dfs(grid, i, j);
}
}
} return res;
}
// 从 (i, j) 开始,将与之相邻的陆地都变成海水 voiddfs(char[][] grid, int i, int j){ int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return;
} if (grid[i][j] == '0') { // 已经是海水了 return;
} // 将 (i, j) 变成海水
grid[i][j] = '0'; // 淹没上下左右的陆地
dfs(grid, i 1, j);
dfs(grid, i, j 1);
dfs(grid, i - 1, j);
dfs(grid, i, j - 1);
}
为什么每次遇到岛屿,都要用 DFS 算法把岛屿「淹了」呢?主要是为了省事,避免维护visited数组。
// 主函数:计算封闭岛屿的数量 intclosedIsland(int[][] grid){ int m = grid.length, n = grid[0].length; for (int j = 0; j < n; j ) { // 把靠上边的岛屿淹掉
dfs(grid, 0, j); // 把靠下边的岛屿淹掉
dfs(grid, m - 1, j);
} for (int i = 0; i < m; i ) { // 把靠左边的岛屿淹掉
dfs(grid, i, 0); // 把靠右边的岛屿淹掉
dfs(grid, i, n - 1);
} // 遍历 grid,剩下的岛屿都是封闭岛屿 int res = 0; for (int i = 0; i < m; i ) { for (int j = 0; j < n; j ) { if (grid[i][j] == 0) {
res ;
dfs(grid, i, j);
}
}
} return res;
}
// 从 (i, j) 开始,将与之相邻的陆地都变成海水 voiddfs(int[][] grid, int i, int j){ int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { return;
} if (grid[i][j] == 1) { // 已经是海水了 return;
} // 将 (i, j) 变成海水
grid[i][j] = 1; // 淹没上下左右的陆地
dfs(grid, i 1, j);
dfs(grid, i, j 1);
dfs(grid, i - 1, j);
dfs(grid, i, j - 1);
}
只要提前把靠边的陆地都淹掉,然后算出来的就是封闭岛屿了。
intnumEnclaves(int[][] grid){ int m = grid.length, n = grid[0].length; // 淹掉靠边的陆地 for (int i = 0; i < m; i ) {
dfs(grid, i, 0);
dfs(grid, i, n - 1);
} for (int j = 0; j < n; j ) {
dfs(grid, 0, j);
dfs(grid, m - 1, j);
}
// 数一数剩下的陆地 int res = 0; for (int i = 0; i < m; i ) { for (int j = 0; j < n; j ) { if (grid[i][j] == 1) {
res = 1;
}
}
}
return res;
}
// 和之前的实现类似 voiddfs(int[][] grid, int i, int j){ // ...
}
篇幅所限,具体代码我就不写了,我们继续看其他的岛屿问题。
intmaxAreaOfIsland(int[][] grid){ // 记录岛屿的最大面积 int res = 0; int m = grid.length, n = grid[0].length; for (int i = 0; i < m; i ) { for (int j = 0; j < n; j ) { if (grid[i][j] == 1) { // 淹没岛屿,并更新最大岛屿面积
res = Math.max(res, dfs(grid, i, j));
}
}
} return res;
}
// 淹没与 (i, j) 相邻的陆地,并返回淹没的陆地面积 intdfs(int[][] grid, int i, int j){ int m = grid.length, n = grid[0].length; if (i < 0 || j < 0 || i >= m || j >= n) { // 超出索引边界 return0;
} if (grid[i][j] == 0) { // 已经是海水了 return0;
} // 将 (i, j) 变成海水
grid[i][j] = 0;
return dfs(grid, i 1, j)
dfs(grid, i, j 1)
dfs(grid, i - 1, j)
dfs(grid, i, j - 1) 1;
}
解法和之前相比差不多,我也不多说了,接下来的两道岛屿问题是比较有技巧性的,我们重点来看一下。
子岛屿数量
如果说前面的题目都是模板题,那么力扣第 1905 题「统计子岛屿」可能得动动脑子了:
这道题的关键在于,如何快速判断子岛屿?肯定可以借助 Union Find 并查集 算法来判断,不过本文重点在 DFS 算法,就不展开并查集算法了。
什么情况下grid2中的一个岛屿B是grid1中的一个岛屿A的子岛?
当岛屿B中所有陆地在岛屿A中也是陆地的时候,岛屿B是岛屿A的子岛。
反过来说,如果岛屿B中存在一片陆地,在岛屿A的对应位置是海水,那么岛屿B就不是岛屿A的子岛。
那么,我们只要遍历grid2中的所有岛屿,把那些不可能是子岛的岛屿排除掉,剩下的就是子岛。
依据这个思路,可以直接写出下面的代码:
intcountSubIslands(int[][] grid1, int[][] grid2){ int m = grid1.length, n = grid1[0].length; for (int i = 0; i < m; i ) { for (int j = 0; j < n; j ) { if (grid1[i][j] == 0