LeetCode 684. 冗余连接
题目描述


题意分析
输入是一棵无向树再添加一条额外边得到的连通图,需要删除一条边恢复成树;若有多种选择,返回输入顺序最靠后的那条。
树中任意两点原本只有一条路径,加边以后形成唯一的环。删除环上的任意一条边都能恢复成树,删除环外边却会断开连通性,因此要找的是唯一环上最后出现的边。
解法:并查集判环
核心思路
[!blue]
按输入顺序处理边,用并查集维护已经加入的边所形成的连通分量。每个节点开始独立成组,
find(x)返回其代表根;两点的根相同,就表示已有一条路径把它们连起来。对新边
(u,v),如果两端根不同,加入这条边只会连接两个分量,不会成环,合并两个根即可。如果两端根相同,已有路径加上当前新边就会闭合一个环,当前边可以删除。第一次发现成环就能满足“最后出现”的要求,是因为整个输入只有一个环。在该环最后一条边被处理之前,其余已扫描边不可能构成另一个环;最后一条环边到来时,其余环边已经构成连接它两个端点的路径,所以恰好在这里首次发现两端同根。检测到的正是环上输入位置最靠后的边,即使之后还有环外边,也无需继续扫描。
find将沿途节点直接连接到根,压缩后续查找路径;合并时让低秩根接到高秩根下,只有秩相同时才增加保留根的秩。这些操作只改变集合的表示,不改变节点之间已经建立的连通关系。
解题步骤
- 题目中节点编号是
1..n,且边数为n,建立长度为n+1的父节点和秩数组。- 将每个节点的父节点初始化为自身。
- 按输入顺序读取边,先查找两端代表根。
- 根相同就立即返回当前边;根不同则按秩合并,并继续处理。
- 题目保证输入由树加一条边得到,因此一定会找到一次成环连接。
代码实现
class Solution {
private int[] parent;
private int[] rank;
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
parent = new int[n + 1];
rank = new int[n + 1];
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
for (int[] e : edges) {
int u = e[0];
int v = e[1];
// 两端已有连通路径,这条边正好闭合唯一的环。
if (find684(u) == find684(v)) {
return e;
}
union684(u, v);
}
return new int[0];
}
private int find684(int x) {
if (parent[x] != x) {
parent[x] = find684(parent[x]);
}
return parent[x];
}
private void union684(int a, int b) {
int pa = find684(a);
int pb = find684(b);
if (pa == pb) {
return;
}
if (rank[pa] < rank[pb]) {
parent[pa] = pb;
} else if (rank[pa] > rank[pb]) {
parent[pb] = pa;
} else {
parent[pb] = pa;
rank[pa]++;
}
}
}
func findRedundantConnection(edges [][]int) []int {
n := len(edges)
parent := make([]int, n+1)
rank := make([]int, n+1)
for i := 1; i <= n; i++ {
parent[i] = i
}
var find func(x int) int
find = func(x int) int {
if parent[x] != x {
// 查找时将沿途节点直接连接到根,缩短后续查找路径。
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(x, y int) bool {
rootX, rootY := find(x), find(y)
if rootX == rootY {
return false
}
// 按秩合并,只有两棵树秩相同时才提高新根的秩。
if rank[rootX] < rank[rootY] {
parent[rootX] = rootY
} else if rank[rootX] > rank[rootY] {
parent[rootY] = rootX
} else {
parent[rootY] = rootX
rank[rootX]++
}
return true
}
for _, edge := range edges {
x, y := edge[0], edge[1]
// 两端已有连通路径,这条边正好闭合唯一的环。
if !union(x, y) {
return edge
}
}
return nil
}
复杂度分析
- 时间复杂度:$O(n\alpha(n))$。最多扫描
n条边,路径压缩与按秩合并使每次并查集操作的摊还时间为 $O(\alpha(n))$。- 空间复杂度:$O(n)$。父节点、秩数组及查找的递归栈都包含在这一上界内。
关键点总结
[!green]
- 成环依据是两端已存在连通路径,不是两个节点是否都曾出现过。
- 唯一环上最后处理的边,会触发第一次同根检测,两种顺序要求并不矛盾。
- 环外的边不属于可删除答案,即使它出现在输入最后也不能返回它。
- 合并前判断连通性,合并和路径压缩都要围绕代表根进行。
易错点总结
[!yellow]
- 端点都出现过就判为冗余,它们可能仍属于两个不同分量。
- 先合并再比较根,会让每一条正常边也表现为两端同根。
- 返回最先出现的环内边,不符合输入顺序最靠后的要求;应等环被最后一条边闭合。
- 把代表根当成固定的最小编号,没有必要,代表只需保持集合归属正确。
- 将本结论直接套到一般有向图,会忽略有向边的方向和入度条件。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 261. 以图判树 | 中等 | 同样判断无向图中的环和树结构,本题按边顺序找到使既有连通分量形成环的那条边。 |
| 685. 冗余连接 II | 困难 | 加入有向关系后还要处理入度为2,不能只用无向并查集找环。 |
| 547. 省份数量 | 中等 | 用并查集合并连通分量;本题找到使两端已经连通的多余边,该题按邻接矩阵合并城市。 |
| 1319. 连通网络的操作次数 | 中等 | 用并查集合并连通分量;本题找到使两端已经连通的多余边,该题统计网络连通分量与可用冗余边。 |
| 721. 账户合并 | 中等 | 用并查集合并连通分量;本题找到使两端已经连通的多余边,该题按共享邮箱合并账户。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!