# 使用并查集解决的相关问题

CSDN：使用并查集解决的相关问题

## 相关题目

### LeetCode 547. 省份数量

public static int findCircleNum(int[][] m) {
int n = m.length;
UF uf = new UF(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (m[i][j] == 1) {
uf.union(i, j);
}
}
}
return uf.setSize();
}

public static class UF {
int[] parent;
int[] help;
int[] size;
int sets;

public UF(int n) {
size = new int[n];
parent = new int[n];
help = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
sets = n;
}

public void union(int i, int j) {
if (i == j) {
return;
}
int p1 = find(i);
int p2 = find(j);
if (p2 != p1) {
int size1 = size[p1];
int size2 = size[p2];
if (size1 > size2) {
parent[p2] = p1;
size[p1] += size2;
} else {
parent[p1] = p2;
size[p2] += size1;
}
sets--;
}
}

public int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (int index = 0; index < hi; index++) {
parent[help[index]] = i;
}
return i;
}

public int setSize() {
return sets;
}
}


### LeetCode 305. 岛屿数量II

    public static List<Integer> numIslands2(int m, int n, int[][] positions) {
UF uf = new UF(m, n);
List<Integer> ans = new ArrayList<>();
for (int[] position : positions) {
}
return ans;
}

public static class UF {
int[] help;
int[] parent;
int[] size;
int sets;
int row;
int col;

public UF(int m, int n) {
row = m;
col = n;
int len = m * n;
help = new int[len];
size = new int[len];
parent = new int[len];
}

private int index(int i, int j) {
return i * col + j;
}

private void union(int i1, int j1, int i2, int j2) {
if (i1 < 0 || i2 < 0 || i1 >= row || i2 >= row || j1 < 0 || j2 < 0 || j1 >= col || j2 >= col) {
return;
}

int f1 = index(i1, j1);
int f2 = index(i2, j2);
if (size[f1] == 0 || size[f2] == 0) {
// 重要：如果两个都不是岛屿，则不用合并
return;
}
f1 = find(f1);
f2 = find(f2);
if (f1 != f2) {
int s1 = size[f1];
int s2 = size[f2];
if (s1 >= s2) {
size[f1] += s2;
parent[f2] = f1;
} else {
size[f2] += s1;
parent[f1] = f2;
}
sets--;
}
}

public int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (int index = 0; index < hi; index++) {
parent[help[index]] = i;
}
return i;
}

public int connect(int i, int j) {
int index = index(i, j);
if (size[index] == 0) {
sets++;
size[index] = 1;
parent[index] = index;
// 去四个方向union
union(i - 1, j, i, j);
union(i, j - 1, i, j);
union(i + 1, j, i, j);
union(i, j + 1, i, j);
}
// index上本来就有岛屿，所以不需要处理
return sets;
}
}


### LeetCode 130. 被围绕的区域

LeetCode 200. 岛屿数量问题一样，这个题目也有DFS和并查集两种解决方法。DFS方法的思路是，先遍历最外圈（即：第一行，第一列，最后一行，最后一列），最外圈中的元素如果为O，则做如下事情：

DFS解法的完整代码见：

    public static void solve(char[][] board) {
if (board == null || board.length == 0 || board[0] == null || board[0].length == 0) {
return;
}
int m = board.length;
int n = board[0].length;
for (int i = 0; i < m; i++) {
if (board[i][0] == 'O') {
free(i, 0, board);
}
if (board[i][n - 1] == 'O') {
free(i, n - 1, board);
}
}
for (int i = 0; i < n; i++) {
if (board[0][i] == 'O') {
free(0, i, board);
}
if (board[m - 1][i] == 'O') {
free(m - 1, i, board);
}
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
board[i][j] = (board[i][j] != '#' ? 'X' : 'O');
}
}
}

public static void free(int i, int j, char[][] board) {
int m = board.length;
int n = board[0].length;
if (i < 0 || i >= m || j < 0 || j >= n || board[i][j] != 'O') {
return;
}
board[i][j] = '#';
free(i + 1, j, board);
free(i - 1, j, board);
free(i, j + 1, board);
free(i, j - 1, board);
}


public static void solve(char[][] board) {
if (board == null || board.length <= 2 || board[0].length <= 2) {
return;
}
int m = board.length;
int n = board[0].length;
// 所有周边为O的点的代表节点都设置为dump
int dump = 0;
UF uf = new UF(m, n);
// 第一列和最后一列O字符的元素和dump点union一下
for (int i = 0; i < m; i++) {
if (board[i][0] == 'O') {
uf.union(dump, i, 0);
}
if (board[i][n - 1] == 'O') {
uf.union(dump, i, n - 1);
}
}
// 第一行和最后一行O字符的元素和dump点union一下
for (int i = 0; i < n; i++) {
if (board[0][i] == 'O') {
uf.union(dump, 0, i);
}
if (board[m - 1][i] == 'O') {
uf.union(dump, m - 1, i);
}
}
// 其他位置，只要是O字符，就和上下左右的O字符union一下
for (int i = 1; i < m - 1; i++) {
for (int j = 1; j < n - 1; j++) {
if (board[i][j] == 'O') {
int index = uf.index(i, j);
if (board[i - 1][j] == 'O') {
uf.union(index, i - 1, j);
}
if (board[i][j - 1] == 'O') {
uf.union(index, i, j - 1);
}
if (board[i + 1][j] == 'O') {
uf.union(index, i + 1, j);
}
if (board[i][j + 1] == 'O') {
uf.union(index, i, j + 1);
}
}
}
}
// 最后判断哪些不是和dump点在同一集合的O点，这些都会被X吞没
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'O' && !uf.isSameSet(dump, i, j)) {
board[i][j] = 'X';
}
}
}
}

public static class UF {
int[] help;
int[] size;
int[] parent;
int row;
int col;

public UF(int m, int n) {
row = m;
col = n;
// 多一个位置，用于存dump
int len = m * n + 1;
help = new int[len];
size = new int[len];
parent = new int[len];
for (int i = 1; i < len; i++) {
parent[i] = i;
size[i] = 1;
}
}

public int index(int i, int j) {
return i * col + j + 1;
}

private int find(int i) {
int hi = 0;
while (i != parent[i]) {
help[hi++] = i;
i = parent[i];
}
for (int index = 0; index < hi; index++) {
parent[help[index]] = i;
}
return i;
}

public void union(int p1, int i, int j) {
int p2 = index(i, j);
int f1 = find(p1);
int f2 = find(p2);
if (f1 != f2) {
int s1 = size[f1];
int s2 = size[f2];
if (s1 >= s2) {
size[f1] += s2;
parent[f2] = f1;
} else {
size[f2] += s1;
parent[f1] = f2;
}
}
}

public boolean isSameSet(int p, int i, int j) {
return find(p) == find(index(i, j));
}
}


### LintCode 178. 图是否是树

1. 如果n等于0，默认就是空树，直接返回true

2. 如果n - 1 != 边数量，说明不是树，因为对于有n个点的树，边的数量一定是n-1

3. 最重要的一个判断条件：如果一个边中的的两个点的代表节点一样，说明出现了环，所以，最后union掉所有边的所有节点，如果集合个数不等于1，说明有环，直接返回false

    public static boolean validTree(int n, int[][] edges) {
if (n == 0) {
return true;
}
if (n - 1 != edges.length) {
return false;
}
LeetCode_0261_GraphValidTree.UnionFind uf = new LeetCode_0261_GraphValidTree.UnionFind(n);
for (int[] edge : edges) {
uf.union(edge[0], edge[1]);
}
return uf.setSize() == 1;
}

// 如何判断环？ 如果一个node节点中两个点的代表点一样，说明出现了环，直接返回false
public static class UnionFind {
private int[] parents;
private int[] size;
private int[] help;
private int sets;

public UnionFind(int n) {
parents = new int[n];
size = new int[n];
help = new int[n];
for (int i = 0; i < n; i++) {
parents[i] = i;
size[i] = 1;
}
sets = n;
}

public int find(int i) {
int hi = 0;
while (i != parents[i]) {
help[hi++] = i;
i = parents[i];
}
for (int j = 0; j < hi; j++) {
parents[help[j]] = i;
}
return i;

}

public void union(int i, int j) {
int f1 = find(i);
int f2 = find(j);
if (f1 != f2) {
int s1 = size[f1];
int s2 = size[f2];
if (s1 < s2) {
parents[f1] = parents[f2];
size[f2] += s1;
} else {
parents[f2] = parents[f1];
size[f1] += s2;
}
sets--;
}
}

public int setSize() {
return sets;
}
}


### LeetCode 952. 按公因数计算最大组件大小

记录1: key：3，value：6



    public static int largestComponentSize(int[] arr) {
UnionFind uf = new UnionFind(arr.length);
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < arr.length; i++) {
// 以下是找arr[i]有哪些因数的相对比较快的做法。
int num = (int) Math.sqrt(arr[i]);
for (int j = 1; j <= num; j++) {
if (arr[i] % j == 0) {
if (j != 1) {
if (!map.containsKey(j)) {
map.put(j, i);
} else {
// 找到有共同因数的元素了，可以合并了
uf.union(map.get(j), i);
}
}
int other = arr[i] / j;
if (other != 1) {
if (!map.containsKey(other)) {
map.put(other, i);
} else {
// 找到有共同因数的元素了，可以合并了
uf.union(map.get(other), i);
}
}
}
}
}
return uf.maxSize();
}

// 并查集
public static class UnionFind {
private int[] parents;
private int[] size;
private int[] help;

public UnionFind(int len) {
parents = new int[len];
size = new int[len];
help = new int[len];
for (int i = 0; i < len; i++) {
parents[i] = i;
size[i] = 1;
}
}

public int maxSize() {
int ans = 0;
for (int size : size) {
ans = Math.max(ans, size);
}
return ans;
}

private int find(int i) {
int hi = 0;
while (i != parents[i]) {
help[hi++] = i;
i = parents[i];
}
for (int j = 0; j < hi; j++) {
parents[help[j]] = i;
}
return i;
}

// i 和 j分别是两个数的位置，不是值
public void union(int i, int j) {
int f1 = find(i);
int f2 = find(j);
if (f1 != f2) {
int s1 = size[f1];
int s2 = size[f2];
if (s1 > s2) {
parents[f2] = f1;
size[f1] += s2;
} else {
parents[f1] = f2;
size[f2] += s1;
}
}
}
}


### LeetCode 803. 打砖块

public static int[] hitBricks(int[][] grid, int[][] hits) {
for (int[] hit : hits) {
if (grid[hit[0]][hit[1]] == 1) {
grid[hit[0]][hit[1]] = 2;
}
}
UnionFind unionFind = new UnionFind(grid);
int[] ans = new int[hits.length];
for (int i = hits.length - 1; i >= 0; i--) {
if (grid[hits[i][0]][hits[i][1]] == 2) {
ans[i] = unionFind.finger(hits[i][0], hits[i][1]);
}
}
return ans;
}

// 并查集
public static class UnionFind {
private int row;
private int col;
// 连在天花板上的集合的元素个数
private int cellingAll;
// 原始矩阵
private int[][] grid;
// 是否连在天花板上
private boolean[] cellingSet;
private int[] parents;
private int[] size;
private int[] help;

public UnionFind(int[][] matrix) {
grid = matrix;
row = grid.length;
col = grid[0].length;
int all = row * col;
cellingAll = 0;
cellingSet = new boolean[all];
parents = new int[all];
size = new int[all];
help = new int[all];
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 1) {
int index = i * col + j;
parents[index] = index;
size[index] = 1;
if (i == 0) {
cellingSet[index] = true;
cellingAll++;
}
}
}
}
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j] == 1) {
union(i, j, i - 1, j);
union(i, j, i + 1, j);
union(i, j, i, j - 1);
union(i, j, i, j + 1);
}
}
}
}

private int index(int i, int j) {
return i * col + j;
}

private int find(int i) {
int hi = 0;
while (i != parents[i]) {
help[hi++] = i;
i = parents[i];
}
for (int j = 0; j < hi; j++) {
parents[help[j]] = i;
}
return i;
}

private void union(int r1, int c1, int r2, int c2) {
if (valid(r1, c1) && valid(r2, c2)) {
int father1 = find(index(r1, c1));
int father2 = find(index(r2, c2));
if (father1 != father2) {
int size1 = size[father1];
int size2 = size[father2];
boolean status1 = cellingSet[father1];
boolean status2 = cellingSet[father2];
if (size1 <= size2) {
parents[father1] = father2;
size[father2] = size1 + size2;
if (status1 ^ status2) {
cellingSet[father2] = true;
cellingAll += status1 ? size2 : size1;
}
} else {
parents[father2] = father1;
size[father1] = size1 + size2;
if (status1 ^ status2) {
cellingSet[father1] = true;
cellingAll += status1 ? size2 : size1;
}
}
}
}
}

private boolean valid(int row, int col) {
return row >= 0 && row < this.row && col >= 0 && col < this.col;
}

public int finger(int i, int j) {
grid[i][j] = 1;
int cur = i * col + j;
if (i == 0) {
cellingSet[cur] = true;
cellingAll++;
}
parents[cur] = cur;
size[cur] = 1;
int pre = cellingAll;
union(i, j, i - 1, j);
union(i, j, i + 1, j);
union(i, j, i, j - 1);
union(i, j, i, j + 1);
int now = cellingAll;
if (i == 0) {
return now - pre;
} else {
return now == pre ? 0 : now - pre - 1;
}
}
}


## 更多

posted @ 2022-06-04 23:15  Grey Zeng  阅读(276)  评论(0编辑  收藏  举报