题目描述
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
涉及tag
dfs;bfs;并查集
网格问题
网格问题类比二叉树问题,下面给出网格问题的框架代码:
void dfs(int[][] grid, int r, int c) {
// 判断退出递归条件
// 如果坐标 (r, c) 超出了网格范围,直接返回
if (!inArea(grid, r, c)) {
return;
}
// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}
// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
算法思路
用经典dfs模板,注意两个特殊判断
也可以用bfs,遍历到res++
并查集:
将每个标注为1的格子看成是一个岛屿,使用并查集进行连通分量的合并,记录合并次数,最后用总数减去合并次数就是岛屿的数量。
需要注意初始化时要将二维grid[i][j]映射到一维parents[k]中,k = i * COL + j,COL是grid的列数。初始化parents: grid[i][j] = 1时 parents[k] = k,size[k] = 1。
示例代码1(dfs)
为避免重复遍历,设置状态变量grid[r][c] = 2;
class Solution {
public int numIslands(char[][] grid) {
int res = 0;
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
//把该网格周围的所有网格都遍历到,才能新增一个岛屿数量
if (grid[i][j] == '1') {
dfs(grid, i, j);
res++;
}
}
}
return res;
}
public void dfs(char[][] grid, int row, int col) {
if (row < 0 || col < 0 || row >= grid.length || col >= grid[0].length) return;
if (grid[row][col] != '1')
return;
grid[row][col] = '2';
dfs(grid, row + 1, col);
dfs(grid, row - 1, col);
dfs(grid, row, col + 1);
dfs(grid, row, col - 1);
}
}
示例代码2(bfs)
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
for(int i = 0; i < grid.length; i++) {
for(int j = 0; j < grid[0].length; j++) {
if(grid[i][j] == '1'){
bfs(grid, i, j);
count++;
}
}
}
return count;
}
//把找到的每一个岛屿都变成海洋,再找下一个
private void bfs(char[][] grid, int i, int j){
Queue<int[]> list = new LinkedList<>();
list.add(new int[] { i, j });
while(!list.isEmpty()){
int[] cur = list.remove();
i = cur[0];
j = cur[1];
if(0 <= i && i < grid.length && 0 <= j && j < grid[0].length && grid[i][j] == '1') {
grid[i][j] = '0';
list.add(new int[] { i + 1, j });
list.add(new int[] { i - 1, j });
list.add(new int[] { i, j + 1 });
list.add(new int[] { i, j - 1 });
}
}
}
}
示例代码3
class Solution {
int islandsCount = 0;
int mergedCount = 0;
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
UnionFind uf = new UnionFind(grid);
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
int landx = i * n + j;
if(grid[i][j] == '1'){ // 检测右侧和下侧是否连通,连通时合并之(用并列的if,在union中会检测是否属于同一集合)
if(j < n - 1 && grid[i][j + 1] == '1') { // 右侧
uf.union(landx, landx + 1);
}
if(i < m - 1 && grid[i + 1][j] == '1') { // 下侧
uf.union(landx, landx + n);
}
}
}
}
return islandsCount - mergedCount;
}
private class UnionFind{
private int[] parents;
private int[] rank;
public UnionFind(char[][] grid){
int m = grid.length, n = grid[0].length;
parents = new int[m * n];
rank = new int[m * n];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++) {
if(grid[i][j] == '1'){ // 针对单格岛,累计岛屿数量,赋值parents[k] rank[k]
islandsCount++;
int k = i * n + j;
parents[k] = k;
rank[k] = 1;
}
}
}
}
// 带路径压缩的查找
public int find(int x){
if(parents[x] == x) return x;
return parents[x] = find(parents[x]);
}
// 按秩合并
public void union(int x, int y){
int xRoot = find(x);
int yRoot = find(y);
if(xRoot != yRoot){
mergedCount++; // 只有不属于一个集合时,合并才次数加1
if(rank[yRoot] <= rank[xRoot]) parents[yRoot] = xRoot;// yRoot挂到xRoot上
else parents[xRoot] = yRoot; // xRoot挂到yRoot上
if (rank[xRoot] == rank[yRoot]) rank[xRoot]++; // 秩相同时才加1
}
}
}
}