这道题是LeetCode原题
https://leetcode.com/problems/number-of-islands/
题意:给定一个二维数组表,只可能会有0和1的位置,求上下左右都为1的一个整块数量。像下图就为2个岛数量
递归方式
public static int numIslands(char[][] board) {
int islands = 0;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == '1') {
islands++;
infect(board, i, j);
}
}
}
return islands;
}
// 从(i,j)这个位置出发,把所有练成一片的'1'字符,变成0
public static void infect(char[][] board, int i, int j) {
if (i < 0 || i == board.length || j < 0 || j == board[0].length || board[i][j] != '1') {
return;
}
board[i][j] = 0;
// 列举所有可能性
infect(board, i - 1, j);
infect(board, i + 1, j);
infect(board, i, j - 1);
infect(board, i, j + 1);
}
并查集方式实现
定义:
- 有若干个样本 a,b,c,d…类型假设是V
- 在并查集中一开始认为每个样本都在单独的集合里
- 用户可以在任何时候调用如下两个方法:
- boolean isSameSet(V x,V y); 查询样本x和样本y是否属于一个集合
- void union(V a, V b);把x集合和y集合合并起来成为一个集合
- isSameSet和union方法的代价越低越好
public static int numIslands(char[][] board) {
int row = board.length;
int col = board[0].length;
UnionFind2 uf = new UnionFind2(board);
// 只有左的 这样可以不用处理一些边界条件 这样虽然多了for循环,但其实和一个for循环里面有多个判断是一样的 这样也不需要判断的是否越界
for (int j = 1; j < col; j++) {
// (0-j) (0,0)跳过了 (0,1)(0,2)(0,3)
if (board[0][j - 1] == '1' && board[0][j] == '1') {
uf.union(0, j - 1, 0, j);
}
}
// 只有上的
for (int i = 1; i < row; i++) {
// 如果我自己是1和上面也是1
if (board[i - 1][0] == '1' && board[i][0] == '1') {
uf.union(i - 1, 0, i, 0);
}
}
// 既有左又有上的
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
if (board[i][j] == '1') {
if (board[i][j - 1] == '1') {
uf.union(i, j - 1, i, j);
}
if (board[i - 1][j] == '1') {
uf.union(i - 1, j, i, j);
}
}
}
}
return uf.sets();
}
public static class UnionFind2 {
private int[] parent;
private int[] size;
private int[] help;
// (i,j) i*列数+j
private int col;
private int sets;
public UnionFind2(char[][] board) {
col = board[0].length;
sets = 0;
int row = board.length;
int len = row * col;
parent = new int[len];
size = new int[len];
help = new int[len];
for (int r = 0; r < row; r++) {
for (int c = 0; c < col; c++) {
if (board[r][c] == '1') {
int i = index(r, c);
parent[i] = i;
size[i] = 1;
sets++;
}
}
}
}
// (r,c) -> i 对应的下标给算出来
private int index(int r, int c) {
return r * col + c;
}
// 原始位置 -> 下标
private int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (hi--; hi >= 0; hi--) {
parent[help[hi]] = i;
}
return i;
}
public void union(int r1, int c1, int r2, int c2) {
int i1 = index(r1, c1);
int i2 = index(r2, c2);
int f1 = find(i1);
int f2 = find(i2);
if (f1 != f2) {
if (size[f1] >= size[f2]) {
size[f1] += size[f2];
parent[f2] = f1;
} else {
size[f2] += size[f1];
parent[f1] = f2;
}
sets--;
}
}
public int sets() {
return sets;
}
}