LeetCode刷题实战:用并查集(Union-Find)秒杀“朋友圈”和“岛屿数量”这类题目(附Python/Java代码)
·
并查集实战:用Union-Find高效解决LeetCode朋友圈与岛屿问题
在算法面试中,并查集(Union-Find)是一种常被忽视却威力巨大的数据结构。它能在近乎常数时间内完成集合合并与查询操作,特别适合处理动态连通性问题。本文将以LeetCode经典题目547号「朋友圈」和200号「岛屿数量」为例,手把手教你如何识别并查集适用场景,并给出可直接套用的Python/Java代码模板。
1. 并查集核心原理与优化策略
并查集本质上是通过森林结构维护动态连通关系的工具。其核心操作包含:
- Find:查找元素所属集合(即根节点)
- Union:合并两个元素所在的集合
- Connected:判断两个元素是否连通
1.1 三种实现方式对比
| 实现方式 | Find时间复杂度 | Union时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|---|
| Quick-Find | O(1) | O(N) | O(N) | 查询频繁但合并少的场景 |
| Quick-Union | O(tree height) | O(tree height) | O(N) | 一般情况 |
| 加权Quick-Union | O(logN) | O(logN) | O(N) | 大规模数据合并 |
# 加权Quick-Union Python实现
class UnionFind:
def __init__(self, size):
self.root = [i for i in range(size)]
self.rank = [1] * size
def find(self, x):
while x != self.root[x]:
x = self.root[x]
return x
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] > self.rank[rootY]:
self.root[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.root[rootX] = rootY
else:
self.root[rootY] = rootX
self.rank[rootX] += 1
提示:路径压缩优化可以在find操作中将节点直接链接到根节点,进一步降低树高。只需在find方法中添加一行:
self.root[x] = self.root[self.root[x]]
2. LeetCode 547:朋友圈问题实战
题目描述:一个班级中有N个学生,给出M×M的矩阵表示朋友关系(1表示直接朋友),求朋友圈总数。
2.1 问题转化技巧
将每个学生视为节点,朋友关系视为边。朋友圈数量等于连通分量的数量:
- 初始化并查集,每个学生独立成集合
- 遍历矩阵,对每个为1的关系执行union操作
- 最终统计根节点数量即为答案
// Java解法
class Solution {
public int findCircleNum(int[][] M) {
int n = M.length;
UnionFind uf = new UnionFind(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.count();
}
class UnionFind {
private int[] parent;
private int count;
public UnionFind(int n) {
parent = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
}
count = n;
}
public int find(int p) {
while (p != parent[p]) {
parent[p] = parent[parent[p]]; // 路径压缩
p = parent[p];
}
return p;
}
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ) return;
parent[rootP] = rootQ;
count--;
}
public int count() {
return count;
}
}
}
2.2 复杂度分析
- 时间复杂度:O(N²α(N)),其中α为反阿克曼函数,通常认为接近O(1)
- 空间复杂度:O(N)用于存储父节点数组
3. LeetCode 200:岛屿数量问题
题目描述:给定由'1'(陆地)和'0'(水)组成的二维网格,计算岛屿数量(被水包围的陆地连通区域)。
3.1 并查集解法步骤
- 初始化:将每个'1'视为独立岛屿,'0'不处理
- 遍历网格,对每个'1'检查其右侧和下侧相邻格子
- 如果相邻也是'1'则执行union操作
- 最终统计根节点数量即为岛屿数
# Python解法
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
if not grid:
return 0
rows, cols = len(grid), len(grid[0])
uf = UnionFind(rows * cols)
directions = [(1,0), (0,1)] # 只需检查右和下
count = 0
for i in range(rows):
for j in range(cols):
if grid[i][j] == '1':
count += 1
idx = i * cols + j
for di, dj in directions:
ni, nj = i + di, j + dj
if 0 <= ni < rows and 0 <= nj < cols and grid[ni][nj] == '1':
nidx = ni * cols + nj
if not uf.connected(idx, nidx):
uf.union(idx, nidx)
count -= 1
return count
3.2 优化技巧
- 虚拟节点法:为所有水域创建公共父节点,减少union操作
- 按秩合并:在union时总是将小树合并到大树下,保持树平衡
- 一维映射:将二维坐标线性化为一维索引,简化处理
4. 并查集解题模板与调试技巧
4.1 通用解题模板
- 识别问题类型:涉及动态连通性、分组或聚类的问题
- 设计节点与边:确定什么作为节点,什么关系触发union
- 初始化结构:根据数据规模创建并查集实例
- 处理边界条件:如空输入、单个元素等特殊情况
- 统计结果:通过count或遍历计算连通分量
4.2 常见错误排查
- 数组越界:确保find操作中的索引有效
- 初始化错误:父数组应初始化为各自索引
- 重复合并:union前应先检查是否已连通
- 方向遗漏:在网格问题中确保检查所有必要方向
// 调试示例:打印并查集状态
void debugPrint(UnionFind uf, int n) {
System.out.print("Parent: ");
for (int i = 0; i < n; i++) {
System.out.print(uf.find(i) + " ");
}
System.out.println();
}
掌握并查集不仅能解决特定算法题,更能培养将实际问题抽象为连通性问题的思维能力。建议在理解基础实现后,尝试解决LeetCode 128(最长连续序列)、130(被围绕的区域)等扩展题目来巩固这一技术。
更多推荐


所有评论(0)