并查集
问题
Java 中并查集(Union-Find)的原理和常见题目有哪些?
答案
并查集用于处理动态连通性问题:合并集合、查询两元素是否属于同一集合。
并查集模板
路径压缩 + 按秩合并
class UnionFind {
int[] parent;
int[] rank;
int count; // 连通分量数
UnionFind(int n) {
parent = new int[n];
rank = new int[n];
count = n;
for (int i = 0; i < n; i++) parent[i] = i;
}
// 查找根节点(路径压缩)
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
// 合并两个集合(按秩合并)
void union(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX == rootY) return;
if (rank[rootX] < rank[rootY]) { int tmp = rootX; rootX = rootY; rootY = tmp; }
parent[rootY] = rootX;
if (rank[rootX] == rank[rootY]) rank[rootX]++;
count--;
}
boolean connected(int x, int y) { return find(x) == find(y); }
}
复杂度
路径压缩 + 按秩合并后,find 和 union 的均摊时间复杂度为 ,其中 是阿克曼函数的反函数,实际可视为 。
朋友圈 / 省份数量(LeetCode 547)
并查集求连通分量数
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
UnionFind uf = new UnionFind(n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (isConnected[i][j] == 1) uf.union(i, j);
}
}
return uf.count;
}
冗余连接(LeetCode 684)
加边时成环即为冗余
public int[] findRedundantConnection(int[][] edges) {
UnionFind uf = new UnionFind(edges.length + 1);
for (int[] edge : edges) {
if (uf.connected(edge[0], edge[1])) return edge; // 已连通再加边 = 冗余
uf.union(edge[0], edge[1]);
}
return new int[0];
}
岛屿数量(并查集解法)
二维转一维,合并相邻陆地
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
UnionFind uf = new UnionFind(m * n);
int water = 0;
int[][] dirs = {{1, 0}, {0, 1}};
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '0') { water++; continue; }
for (int[] d : dirs) {
int ni = i + d[0], nj = j + d[1];
if (ni < m && nj < n && grid[ni][nj] == '1') {
uf.union(i * n + j, ni * n + nj);
}
}
}
}
return uf.count - water;
}
常见面试问题
Q1: 并查集 vs BFS/DFS 求连通分量?
答案:
| 对比 | 并查集 | BFS/DFS |
|---|---|---|
| 动态加边 | ✅ 支持 | ❌ 需重新遍历 |
| 时间复杂度 | 近 每次操作 | 全量遍历 |
| 适用场景 | 动态连通性、在线查询 | 静态图遍历 |
Q2: 路径压缩有什么作用?
答案:
find 时直接把节点挂到根节点下,使树高度趋近于 1,后续查找几乎 。