想要精通算法和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;
// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1
if (grid[i][j] == '1') {
rank[i * len2 + j] = 1;
// 岛屿数量+1
sum++;
}
}
}
}
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;
}
// 岛屿数量-1
sum--;
}
}
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;
// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1
if (grid[i][j] == '1') {
rank[i * len2 + j] = 1;
// 岛屿数量+1
sum++;
}
}
}
}
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;
}
// 岛屿数量-1
sum--;
}
}
}
二. 岛屿的最大面积
原题链接
这个题目就是在第一题的基础上,查找最大的集合深度,即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;
// 如果这个地方是岛屿,那么该元素对应的集合内元素数量为1
if (grid[i][j] == 1) {
rank[i * len2 + j] = 1;
// 岛屿数量+1
sum++;
}
}
}
}
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;
}
// 岛屿数量-1
sum--;
}
}
}