题目:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
难度:Medium
示例:
方法一(并查集):
class Solution {
public int numIslands(char[][] grid) {
if(grid == null || grid.length == 0){
return 0;
}
UnionFind uf = new UnionFind(grid);
int row = grid.length;
int col = grid[0].length;
int water = 0;//水域面积
//方向数组
List<int[]> direction = new ArrayList<>();
direction.add(new int[]{0,1});
direction.add(new int[]{0,-1});
direction.add(new int[]{1,0});
direction.add(new int[]{-1,0});
for(int i = 0;i<row;++i){
for(int j = 0;j<col;++j){
if(grid[i][j] == '0'){//水域面积加一
water++;
}else{
//遍历方向数组,模拟搜寻i,j点的上下左右方向
for(int[] loc : direction){
int r = i + loc[0];
int c = j + loc[1];
if(r >= 0 && r < row && c >=0 && c < col && grid[r][c] == '1'){
uf.union(r*col + c,i*col + j);//二维坐标转换为一维
//例如(x,y)对应的一维坐标为x * col + y,(1,2)对应为1*4 + 2 = 6
}
}
}
}
}
return uf.getCount() - water;//合并完之后的count减去水域面积即为岛屿数量
}
//构建并查集
class UnionFind{
//单元总数,初始值为grid矩阵元素总数
int count;
//表示子父节点关系的一维数组
int[] parent;
//构造方法
public UnionFind(char[][] grid){
int row = grid.length;
int col = grid[0].length;
count = row * col;
parent = new int[row * col];
//将一维数组中每个元素初始化为对应索引的值,代表矩阵中每个元素的父节点都为其本身
for(int i = 0;i<row * col;++i){
parent[i] = i;
}
}
//递归查找父节点,返回值为x的父节点
public int find(int x){
if(x == parent[x]){
return x;
}else{
parent[x] = find(parent[x]);
}
return parent[x];
}
//输入二维矩阵中的两个元素,但输入的是转换为一维后的值
public void union(int x,int y){
int rootx = find(x);
int rooty = find(y);
if(rootx != rooty){//不相等,代表父节点不一样,需要合并
parent[rootx] = rooty;
//合并一次count就减一
count--;
}
}
public int getCount(){
return this.count;
}
}
}
效率:
方法二(广度优先遍历BFS):
class Solution {
public int numIslands(char[][] grid) {
if(grid == null || grid.length == 0) return 0;
int[][] directions = {{0,1},{0,-1},{1,0},{-1,0}};//构建上下左右移动二维数组
int count = 0;//记录岛屿数量
int row = grid.length;
int col = grid[0].length;
Queue<int[]> queue = new LinkedList<>();//BFS队列
for(int i = 0;i<row;++i){
for(int j = 0;j<col;++j){//遍历整个grid
if(grid[i][j] == '1'){//若此坐标点等于'1',则岛屿数量加1,且将此坐标点加入队列,并扫描上下左右数组
count++;//岛屿数量加1
queue.offer(new int[]{i,j});//将此坐标点点加入BFS队列
while(!queue.isEmpty()){//BFS
int size = queue.size();
for(int k = 0;k<size;++k){//遍历BFS队列
int[] arr = queue.poll();
for(int[] dir : directions){//依次遍历上下左右移动数组
int newx = arr[0] + dir[0];
int newy = arr[1] + dir[1];
if(newx >= 0 && newy >= 0 && newx < row && newy < col && grid[newx][newy] == '1'){//若上下左右坐标点都没超出grid范围且此时为'1',则执行以下操作
queue.offer(new int[]{newx,newy});//将此坐标点加入BFS队列
grid[newx][newy] = '0';//将此坐标点改为'0',防止下次遍历到重复计数
}
}
}
}
}
}
}
return count;
}
}
效率:
方法三(深度优先遍历DFS):
class Solution {
public int numIslands(char[][] grid) {
if(grid == null || grid.length == 0) return 0;
int count = 0;//岛屿数量
int row = grid.length;
int col = grid[0].length;
for(int i = 0;i<row;++i){
for(int j = 0;j<col;++j){
if(grid[i][j] == '1'){//若此坐标点为'1',则执行if块
count++;
dfs(grid,i,j,row,col);//DFS将此坐标点上下左右改为'0'
}
}
}
return count;
}
public void dfs(char[][] grid,int i,int j,int row,int col){
if(i < 0 || j < 0 || i >= row || j >= col || grid[i][j] == '0'){//判断终止条件
return;
}
grid[i][j] = '0';//将当前坐标点改为'0'
dfs(grid,i+1,j,row,col);//依次修改上下左右坐标点
dfs(grid,i-1,j,row,col);
dfs(grid,i,j+1,row,col);
dfs(grid,i,j-1,row,col);
}
}
效率:
总结:此题三种解法都非常经典,建议都掌握,并多敲几次,达到能够自己立刻码出来的程度。