1. 题目
读题
https://leetcode.cn/problems/number-of-islands/submissions/
考查点
这道题的考察点是:
- 如何使用并查集这种数据结构来解决一些关于集合的问题,例如判断两个元素是否属于同一个集合,或者统计有多少个不相交的集合。
- 如何实现并查集的两个基本操作:查找和合并,并且使用路径压缩的方法来优化查找的效率。
- 如何把二维网格中的元素映射到一维数组中,以便使用并查集的数据结构。
- 如何遍历二维网格中的元素,并且根据相邻关系来合并属于同一个岛屿的元素。
- 如何统计最终有多少个不同的岛屿,即根节点的数量。
2. 解法
思路
使用并查集的思路是:把每个陆地格子看作一个节点,把相邻的陆地节点合并为一个集合,最后统计有多少个不同的集合即可。具体步骤如下:
- 初始化一个一维数组 parent,用来存储每个节点的父节点,初始时每个节点的父节点都是自己。
- 遍历二维网格,对于每个陆地格子,找到其在一维数组中对应的索引,然后检查其上下左右四个方向是否也是陆地,如果是,则调用 merge 函数将两个节点合并为一个集合。
- merge 函数的作用是:找到两个节点各自的根节点(即最终的父节点),如果根节点不同,则将其中一个根节点指向另一个根节点,表示两个集合合并为一个。
- find 函数的作用是:找到一个节点的根节点,并且在查找过程中进行路径压缩,即将沿途的节点都指向根节点,以提高后续查找的效率。
- 最后遍历一维数组 parent,统计有多少个节点的父节点是自己,即有多少个根节点,这就是岛屿的数量。
代码逻辑
- 第一步:定义一个一维数组 parent,用来存储每个节点的父节点,初始时每个节点的父节点都是自己。
- 第二步:定义一个 find 函数,用来查找一个节点的根节点,并进行路径压缩。具体做法是:
- 如果一个节点的父节点是自己,就返回自己。
- 如果一个节点的父节点不是自己,就递归地查找它的父节点的根节点,并且在返回过程中把它的父节点更新为根节点,这样就可以把沿途的节点都指向根节点,提高后续查找的效率。
- 第三步:定义一个 merge 函数,用来合并两个节点所在的集合。具体做法是:
- 先分别查找两个节点的根节点。
- 如果根节点不同,就把其中一个根节点指向另一个根节点,表示两个集合合并为一个。
- 第四步:定义一个 numIslands 函数,用来计算岛屿的数量。具体做法是:
- 如果二维网格为空或者没有元素,就返回 0。
- 获取二维网格的行数和列数,并初始化一维数组 parent 的长度为行数乘以列数。
- 遍历二维网格,对于每个格子:
- 如果当前格子是陆地,就计算它在一维数组中对应的索引。
- 然后检查它的上下左右四个方向是否也是陆地,如果是,就调用 merge 函数将两个节点合并为一个集合。
- 最后遍历一维数组 parent,统计有多少个不同的根节点,并且对应的格子是陆地,这就是岛屿的数量。
具体实现
class Solution { // 定义一个一维数组存储每个节点的父节点 private int[] parent; // 查找一个节点的根节点,并进行路径压缩 private int find(int x) { int fa = parent[x]; if (fa != x) { fa = find(fa); // 把沿途的节点都指向根节点 parent[x] = fa; } return fa; } // 合并两个节点所在的集合 private void merge(int x, int y) { int fx = find(x); int fy = find(y); if (fx != fy) { // 把其中一个根节点指向另一个根节点 parent[fx] = fy; } } public int numIslands(char[][] grid) { if (grid == null || grid.length == 0) return 0; int m = grid.length; // 行数 int n = grid[0].length; // 列数 // 初始化一维数组,长度为 m * n parent = new int[m * n]; for (int i = 0; i < m * n; i++) { // 初始时每个节点都是自己的父节点 parent[i] = i; } // 遍历二维网格 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // 如果当前格子是陆地 if (grid[i][j] == '1') { // 计算其在一维数组中对应的索引 int index = i * n + j; // 检查其上下左右四个方向 // 上方 if (i > 0 && grid[i - 1][j] == '1') { // 如果上方也是陆地,合并两个节点 merge(index, index - n); } // 下方 if (i < m - 1 && grid[i + 1][j] == '1') { // 如果下方也是陆地,合并两个节点 merge(index, index + n); } // 左方 if (j > 0 && grid[i][j - 1] == '1') { // 如果左方也是陆地,合并两个节点 merge(index, index - 1); } // 右方 if (j < n - 1 && grid[i][j + 1] == '1') { // 如果右方也是陆地,合并两个节点 merge(index, index + 1); } } } } // 统计岛屿数量,即根节点的数量 int count = 0; for (int i = 0; i < m * n; i++) { // 如果一个节点的父节点是自己,说明它是一个根节点 if (parent[i] == i) { // 如果对应的格子是陆地,说明它是一个岛屿的根节点 if (grid[i / n][i % n] == '1') { count++; } } } return count; } }