「这是我参与2022首次更文挑战的第30天,活动详情查看:2022首次更文挑战」
题目介绍
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例1
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例2
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 300grid[i][j]的值为'0'或'1'
解题思路
此题涉及连通性问题,可以使用并查集来解决。首先来思考几个关键点:
-
如何将利用连通性来连接两个点? 因为二维数组中的每个点都表示
陆地或者水,假设二维数组的行数m = grid.length,二维数组的列数n = grid[i].length,那么每一个点都可以用i * n + j来转换成数字表示,方便并查集进行连通 -
每个点需要判断几个方向的连通路径?
如图,假设一块陆地的上下左右都是陆地,那么中间的陆地需要判断几个方向的连通路径?因为我们是按行按列依次进行遍历和连通,如果中间的陆地分别与上下左右四个方向进行连通,那么当遍历到右边的陆地时,当前陆地的左边和上边都是前面的陆地连通过的,再次判断就会重复了。因此,在遍历每一块陆地时,只需要判断其左边和上边的是否可以连通,而右边和下边留给后面的陆地去做判断。
解题步骤
- 创建并查集,并查集的大小为
m * n(m = grid.lengthn = grid[0].length) - 遍历二维数组,如果当前位置的值为
1,再判断其上边和左边是否为1,如果为1则进行连通 - 再次遍历二维数组,检查并查集中有多少个集合,就有多少个岛屿
解题代码
var numIslands = function(grid) {
const m = grid.length, n = grid[0].length
const unionSet = new UnionSet(m * n)
// 将每个点转成数字表示
let index = (i, j) => i * n + j
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
// 如果当前位置表示 水,不需要进行连通性判断
if (grid[i][j] === '0') continue
// 当前点不是在最上方,才需要判断上方的点
if (i > 0 && grid[i - 1][j] === '1') unionSet.merge(index(i, j), index(i - 1, j))
// 当前点不是在最左,才需要判断左边的点
if (j > 0 && grid[i][j - 1] === '1') unionSet.merge(index(i, j), index(i, j - 1))
}
}
let sum = 0
for(let i = 0; i < row; i++) {
for(let j = 0; j < col; j++) {
if(grid[i][j] === '1' && unionSet.find(index(i, j)) === index(i, j)) sum++
}
}
return sum
};
// 并查集
class UnionSet {
constructor(n) {
this.fa = []
for (let i = 0; i < n; i++) {
this.fa[i] = i
}
}
find(v) {
if(this.fa[v] === v) return v
const root = this.find(this.fa[v])
this.fa[v] = root
return root
}
merge(a, b) {
const sa = this.find(a), sb = this.find(b)
if (sa === sb) return
this.fa[sa] = sb
}
}