想要精通算法和SQL的成长之路 - 岛屿数量和岛屿的最大面积
- 前言
- 一. 岛屿数量
- 1.1 并查集数据结构构造
- 1.2 使用并查集编码
- 二. 岛屿的最大面积
前言
想要精通算法和SQL的成长之路 - 系列导航
并查集的运用
一. 岛屿数量
原题链接
从这个题目的特性来看,它适合用并查集来解决。对并查集还不清楚的,可以看下前言里面的链接。
1.1 并查集数据结构构造
这里的难点就是:
- 如何将二维数组转化为一维数组。假设二维数组下标
(i,j)
,长len1
,高len2
. - 那么二维下标转化为一维坐标就是:
i*len2 + j
class UnionFind {private int[] parent;private int[] rank;private int sum;public UnionFind(char[][] grid) {// 初始化岛屿数量为0,因为我们还没有遍历数组,不知道岛屿的数量sum = 0;int len1 = grid.length;int len2 = grid[0].length;parent = new int[len1 * len2];rank = new int[len1 * len2];for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 根节点指向自己parent[i * len2 + j] = i * len2 + j;// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1if (grid[i][j] == '1') {rank[i * len2 + j] = 1;// 岛屿数量+1sum++;}}}}public int find(int x) {while (x != parent[x]) {x = parent[x];}return x;}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX == rootY) {return;}// 如果根节点 rootX 的深度 > rootY。if (rank[rootX] > rank[rootY]) {// 那么将以rootY作为根节点的集合加入到rootX对应的集合当中rank[rootX] += rank[rootY];// 同时改变rootY的根节点,指向rootX。parent[rootY] = rootX;} else {// 反之rank[rootY] += rank[rootX];parent[rootX] = rootY;}// 岛屿数量-1sum--;}
}
1.2 使用并查集编码
public int numIslands(char[][] grid) {int len1 = grid.length;int len2 = grid[0].length;UnionFind unionFind = new UnionFind(grid);for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 如果当前是岛屿if (grid[i][j] == '1') {// 先将当前的岛屿标识改变,避免被重复访问grid[i][j] = '0';// 分别朝4个方向,上下左右访问,如果是岛屿,开始合并if (i - 1 >= 0 && grid[i - 1][j] == '1') {unionFind.union(i * len2 + j, (i - 1) * len2 + j);}if (i + 1 < len1 && grid[i + 1][j] == '1') {unionFind.union(i * len2 + j, (i + 1) * len2 + j);}if (j - 1 >= 0 && grid[i][j - 1] == '1') {unionFind.union(i * len2 + j, i * len2 + j - 1);}if (j + 1 < len2 && grid[i][j + 1] == '1') {unionFind.union(i * len2 + j, i * len2 + j + 1);}}}}// 最后返回岛屿的数量return unionFind.sum;
}
最终完整代码如下:
public class Test200 {public int numIslands(char[][] grid) {int len1 = grid.length;int len2 = grid[0].length;UnionFind unionFind = new UnionFind(grid);for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 如果当前是岛屿if (grid[i][j] == '1') {// 先将当前的岛屿标识改变,避免被重复访问grid[i][j] = '0';// 分别朝4个方向,上下左右访问,如果是岛屿,开始合并if (i - 1 >= 0 && grid[i - 1][j] == '1') {unionFind.union(i * len2 + j, (i - 1) * len2 + j);}if (i + 1 < len1 && grid[i + 1][j] == '1') {unionFind.union(i * len2 + j, (i + 1) * len2 + j);}if (j - 1 >= 0 && grid[i][j - 1] == '1') {unionFind.union(i * len2 + j, i * len2 + j - 1);}if (j + 1 < len2 && grid[i][j + 1] == '1') {unionFind.union(i * len2 + j, i * len2 + j + 1);}}}}// 最后返回岛屿的数量return unionFind.sum;}class UnionFind {private int[] parent;private int[] rank;private int sum;public UnionFind(char[][] grid) {// 初始化岛屿数量为0,因为我们还没有遍历数组,不知道岛屿的数量sum = 0;int len1 = grid.length;int len2 = grid[0].length;parent = new int[len1 * len2];rank = new int[len1 * len2];for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 根节点指向自己parent[i * len2 + j] = i * len2 + j;// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1if (grid[i][j] == '1') {rank[i * len2 + j] = 1;// 岛屿数量+1sum++;}}}}public int find(int x) {while (x != parent[x]) {x = parent[x];}return x;}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX == rootY) {return;}// 如果根节点 rootX 的深度 > rootY。if (rank[rootX] > rank[rootY]) {// 那么将以rootY作为根节点的集合加入到rootX对应的集合当中rank[rootX] += rank[rootY];// 同时改变rootY的根节点,指向rootX。parent[rootY] = rootX;} else {// 反之rank[rootY] += rank[rootX];parent[rootX] = rootY;}// 岛屿数量-1sum--;}}
}
二. 岛屿的最大面积
原题链接
这个题目就是在第一题的基础上,查找最大的集合深度,即rank
的最大值。我们只需要在第一题的基础上,增加一个循环判断即可:
int maxArea = 0;
for (int i = 0; i < len1 * len2; i++) {maxArea = Math.max(maxArea, unionFind.rank[i]);
}
return maxArea;
注意:
- 本题是int类型的数组,你可以全局替换一下字符:将
'
替换成空
。char
替换成int
最终完整代码如下:
public class Test695 {public int maxAreaOfIsland(int[][] grid) {int len1 = grid.length;int len2 = grid[0].length;UnionFind unionFind = new UnionFind(grid);for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 如果当前是岛屿if (grid[i][j] == 1) {// 先将当前的岛屿标识改变,避免被重复访问grid[i][j] = 0;// 分别朝4个方向,上下左右访问,如果是岛屿,开始合并if (i - 1 >= 0 && grid[i - 1][j] == 1) {unionFind.union(i * len2 + j, (i - 1) * len2 + j);}if (i + 1 < len1 && grid[i + 1][j] == 1) {unionFind.union(i * len2 + j, (i + 1) * len2 + j);}if (j - 1 >= 0 && grid[i][j - 1] == 1) {unionFind.union(i * len2 + j, i * len2 + j - 1);}if (j + 1 < len2 && grid[i][j + 1] == 1) {unionFind.union(i * len2 + j, i * len2 + j + 1);}}}}int maxArea = 0;for (int i = 0; i < len1 * len2; i++) {maxArea = Math.max(maxArea, unionFind.rank[i]);}return maxArea;}class UnionFind {private int[] parent;private int[] rank;private int sum;public UnionFind(int[][] grid) {// 初始化岛屿数量为0,因为我们还没有遍历数组,不知道岛屿的数量sum = 0;int len1 = grid.length;int len2 = grid[0].length;parent = new int[len1 * len2];rank = new int[len1 * len2];for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 根节点指向自己parent[i * len2 + j] = i * len2 + j;// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1if (grid[i][j] == 1) {rank[i * len2 + j] = 1;// 岛屿数量+1sum++;}}}}public int find(int x) {while (x != parent[x]) {x = parent[x];}return x;}public void union(int x, int y) {int rootX = find(x);int rootY = find(y);if (rootX == rootY) {return;}// 如果根节点 rootX 的深度 > rootY。if (rank[rootX] > rank[rootY]) {// 那么将以rootY作为根节点的集合加入到rootX对应的集合当中rank[rootX] += rank[rootY];// 同时改变rootY的根节点,指向rootX。parent[rootY] = rootX;} else {// 反之rank[rootY] += rank[rootX];parent[rootX] = rootY;}// 岛屿数量-1sum--;}}
}