【并查集】力扣684:冗余连接
树可以看成是一个连通且 无环 的 无向 图。
给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [, ] 表示图中在 和 之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。
示例:
输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]
输出: [1,4]
题目意思就是:在无向有环图找出一条边,移除它之后该图能够成为一棵树(即无向无环图)。输入是一个二维数组,表示所有的边(对应的两个结点);输出是一个一维数组,表示需要移除的边(对应的两个结点)。
算法步骤:
-
edges 的每个子集合对应一个结点
-
所有结点的 parent 一开始都是自己
parent[i] = i
-
作为索引的 i 指当前结点
-
作为值的 i 指当前结点的代表结点,即结点 i 的父结点
-
-
每次要连接结点 i 和 j 时,将 i 的祖先 的 parant 变为 j 的祖先
-
每次要查询两个结点是否相连时,查找 i 和 j 的祖先是否最终为同一个结点
-
如果两个结点的祖先不一样,就根据规则相认
-
如果有共同祖先,已经认祖归宗了,但是现在又要加上一层连接,就多余了,即为答案
-
要注意的是,刚开始是找以结点为单位找祖先,后面有连接了就是以集合为单位找了。
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges) # 结点个数,也是有环图 edges 中边的条数,即 树的边+1
if n < 3:
return
parent = list(range(n + 1)) # 用列表存储 n 个结点的父结点,初始化为 0 ~ n-1
# find 操作查找给定结点的祖先
def find(i):
if parent[i] != i:
parent[i] = find(parent[i]) # 一直向上找到祖先
return parent[i]
# union 操作将两个集合连在一起
def union(i, j):
parent[find(i)] = find(j) # j 的祖先成为 i 的祖先的祖先
for node1, node2 in edges:
if find(node1) != find(node2): # 两个结点的祖先不一样,根据规则相认
union(node1, node2)
else: # 有共同祖先,删掉这层连接
return [node1, node2]
时间复杂度:O(nlogn),其中 n 是图中的结点个数。需要遍历图中的 n 条边,对于每条边,需要对两个结点查找祖先,如果两个结点的祖先不同则需要进行合并,需要进行 2 次查找和最多 1 次合并。一共需要进行 2n 次查找和最多 n 次合并,因此总时间复杂度是 O(2nlogn)=O(nlogn)。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 O(nlogn),平均情况下的时间复杂度依然是 O(nα(n)),其中 α 为阿克曼函数的反函数,α(n) 可以认为是一个很小的常数。
空间复杂度:O(n),。使用数组 parent 记录每个结点的祖先。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理