解法一:DFS
思路分析:如果访问二维数组的过程中,遇到某个位置是水,那不用管它,它肯定不是岛。如果遇到某个位置是陆地,有两种情况:他可能四周都是水,如果四周都是水那就一个岛了,这毋庸置疑;但也可能四周有与其他陆地相邻紧挨着,这几个陆地连成更大的一片陆地,但不管怎么样,由于题目限定了最外的边界周围都是水,所以不管这一片陆地有多大,是什么歪歪扭扭的形状,它的四周一定是以水为边界的。因此有个结论就是:从看到一个二维数组的某个位置是陆地开始,我就知道有个岛存在了。
但问题又来了,我们计算总的岛屿数量的时候,我们知道”每个陆地都属于一个岛屿“的概念,于是对二维数组每个元素遍历,然后我们遇到了一个陆地,就统计岛屿数量+1。但是,相邻的陆地,其实是同属一个岛屿的,如果按照上面的代码遇到陆地就岛屿数量+1,这就重复计算了。
因此针对上面这个问题,当遍历二维数组的时候,遇到了陆地,我们除了统计岛屿数+1之外,我们还要从这个陆地开始寻找到不断相邻连着的陆地,然后将他们直接从陆地变成水,也就是直接将这个岛直接挖掉变成海。这样就不会有遍历元素时重复判断了。
这个挖陆地的过程是一个从二维数组某个位置的元素开始,不断探访四周的过程。是一个不断重复的过程,可以用深度优先搜索递归处理。
线性扫描整个二维网络,如果某个节点值为1,就以其为起点深度优先搜索DFS,搜索过程访问的每个节点标记为0,统计进行深度优先搜索的次数,即为岛屿数量。
为什么每次遇到陆地(节点值为1),都要用 DFS 算法把陆地「淹了」(节点值置为0)呢?主要是为了省事,避免维护 visited 数组。因为 dfs 函数遍历到值为 0 的位置会直接返回,所以只要把经过的位置都设置为 0,就可以起到不走回头路的作用。
func numIslands(grid [][]byte) int {
res := 0
for i := 0; i<len(grid); i++{
for j:=0; j<len(grid[i]); j++{
if grid[i][j] == '1'{
// DFS的次数即为岛屿数量
res++
dfs(grid, i, j)
}
}
}
return res
}
func dfs(grid [][]byte, i, j int){
if i < 0 || i >= len(grid) || j < 0 || j >= len(grid[0]){
// 索引越界
return
}
if grid[i][j] == '0'{
// 本身已经是水,无需重复淹
return
}
// 将位置(i,j)上的陆地淹没
grid[i][j] = '0'
// 递归淹没上下左右的陆地
dfs(grid, i-1, j)
dfs(grid, i+1, j)
dfs(grid, i, j-1)
dfs(grid, i, j+1)
}