【并查集】力扣684:冗余连接

树可以看成是一个连通且 无环 的 无向 图。

给定往一棵 n 个节点 (节点值 1~n) 的树中添加一条边后的图。添加的边的两个顶点包含在 1 到 n 中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n 的二维数组 edges ,edges[i] = [ai, bi] 表示图中在 aibi 之间存在一条边。

请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n 个节点的树。如果有多个答案,则返回数组 edges 中最后出现的边。

示例:

image
输入: 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 记录每个结点的祖先。

posted @   Vonos  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
点击右上角即可分享
微信分享提示