Leetcode 685.冗余连接II(困难)

题目

在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。

输入一个有向图,该图由一个有着 n n n 个节点(节点值不重复,从 1 到 n n n)的树及一条附加的有向边构成。附加的边包含在 1 到 n n n 中的两个不同顶点间,这条附加的边不属于树中已存在的边。

结果图是一个以边组成的二维数组 e d g e s edges edges 。 每个元素是一对 [ u i , v i ] [u_i, v_i] [ui,vi],用以表示 有向 图中连接顶点 u i u_i ui 和顶点 v i v_i vi 的边,其中 u i u_i ui v i v_i vi 的一个父节点。

返回一条能删除的边,使得剩下的图是有 n n n 个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。

示例 1:
在这里插入图片描述

输入:edges = [[1,2],[1,3],[2,3]]
输出:[2,3]

示例 2:
在这里插入图片描述

输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
输出:[4,1]

提示:

  • n = = e d g e s . l e n g t h n == edges.length n==edges.length
  • 3 < = n < = 1000 3 <= n <= 1000 3<=n<=1000
  • e d g e s [ i ] . l e n g t h = = 2 edges[i].length == 2 edges[i].length==2
  • 1 < = u i , v i < = n 1 <= u_i, v_i <= n 1<=ui,vi<=n

题解

并查集

思路

相比于 无环五向 的问题 684.冗余连接,本题是 有向 图。

且题目说明了每个节点都只有一个父节点,除了根节点没有父节点,因此在多加了一条附加的边之后,可能导致的情况有两种:

  • 附加的边指向根节点,则包括根节点在内的每个节点都有一个父节点,此时图中一定有环路;
  • 附加的边指向非根节点,则恰好有一个节点(即被附加的边指向的节点)有两个根节点,此时图中可能有环路也可能没有环路

要找到附加的边,需要遍历图中的所有边构建出一棵树,在构建树的过程中寻找导致冲突(即导致一个节点有两个父节点)的边以及导致环路出现的边。

具体做法:
首先使用 p a r e n t parent parent 数组记录每个节点的父节点,初始时 p a r e n t [ i ] = i   ( 1 ≤ i ≤ N ) parent[i] = i\ (1 \le i \le N) parent[i]=i (1iN),即每个节点的父节点是其自身。
另外创建并查集,初始时并查集中的每个节点都是一个连通分支,该连通分支的根节点就是该节点本身。
遍历每条边的过程中,维护导致冲突的边和导致环路出现的边,由于只有一条附加边,因此最多有一条导致冲突的边和一条导致环路出现的边。

当访问到边 [ u , v ] [u, v] [u,v] 时,进行如下操作:

  • 如果此时已有 p a r e n t [ v ] ≠ v parent[v] \neq v parent[v]=v,说明 v v v 有两个父节点,将当前的边 [ u , v ] [u, v] [u,v] 记为导致冲突的边;
  • 否则,令 p a r e n t [ v ] = u parent[v] = u parent[v]=u,然后在并查集中分别找到 u u u v v v 的祖先(即各自的连通分支的根节点)。
    • 如果祖先相同,说明这条边导致环路出现,将当前的边 [ u , v ] [u, v] [u,v] 记为导致环路出现的边;
    • 如果祖先不同,则在并查集中将 u u u v v v 进行合并。

根据上述操作,同一条边不可能同时被记为导致冲突的边和导致环路出现的边。如果访问到的边确实同时导致冲突和环路出现,则这条边被记为导致冲突的边

在遍历图中的所有边之后,根据是否存在导致冲突的边和导致环路出现的边,得到附加的边。

如果没有导致冲突的边,说明附加的边一定导致环路出现,而且是在环路中的最后一条被访问到的边,因此附加的边即为导致环路出现的边。

如果有导致冲突的边,记这条边为 [ u , v ] [u,v] [u,v],则有两条边指向 v v v,另一条边为 [ p a r e n t [ v ] , v ] [parent[v],v] [parent[v],v],需要通过判断是否有导致环路的边决定哪条边是附加的边。

  • 如果有导致环路的边,则附加的边不可能是 [ u , v ] [u,v] [u,v](因为 [ u , v ] [u,v] [u,v] 已经被记为导致冲突的边,不可能被记为导致环路出现的边),因此附加的边是 [ p a r e n t [ v ] , v ] [parent[v],v] [parent[v],v]
  • 如果没有导致环路的边,则附加的边是后被访问到的指向 v v v 的边,因此附加的边是 [ u , v ] [u,v] [u,v]

代码

class Solution {
private:

    int find(vector<int> &parent, int x) {
        return parent[x] == x ? x : parent[x] = find(parent, parent[x]);
    }

    void merge(vector<int> &parent, int u, int v) {
        parent[find(parent, u)] = find(parent, v);
    }
    vector<int> ancestor;
public:
    vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        vector<int> parent(edges.size() + 1);
        for (int i = 1; i <= edges.size(); i++) {
            parent[i] = i;
        }
        int conflict = -1;
        int circle = -1;
        for (int i = 0; i < edges.size(); i++) {
            auto &edge = edges[i];
            int node1 = edge[0], node2 = edge[1];
            if (parent[node2] != node2) { //冲突,node2多个父节点,记录可能引起冲突的边数
                conflict = i;
            } else {
                parent[node2] = node1; 
                if (find(parent, node1) == find(parent, node2)) { //成环了
                    circle = i;
                } else {
                    merge(parent, node1, node2);
                }
            }
        }
        
        if (conflict < 0) { //没有导致冲突的边,则附加边只能是导致成环的边
            return edges[circle];
        } else {
            //存在导致冲突的边
            auto &conflictEdge = edges[conflict];
            
            if (circle >= 0) //如果有导致环路的边
                return vector<int>{parent[conflictEdge[1]], conflictEdge[1]}; 
            else 
                return vector<int>{conflictEdge[0], conflictEdge[1]};
        }
    }
};

复杂度分析

  • 时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN),其中 N N N 是图中的节点个数。需要遍历图中的 N N N 条边,对于每条边,需要对两个节点查找祖先,如果两个节点的祖先不同则需要进行合并,需要进行 2 次查找和最多 1 次合并。一共需要进行 2 N 2N 2N 次查找和最多 N N N 次合并,因此总时间复杂度是 O ( 2 N l o g N ) = O ( N l o g N ) O(2NlogN)=O(NlogN) O(2NlogN)=O(NlogN)。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 O ( N l o g N ) O(NlogN) O(NlogN),平均情况下的时间复杂度依然是 O ( N α ( N ) ) O(Nα(N)) O(Nα(N)),其中 α \alpha α 为阿克曼函数的反函数, α ( N ) \alpha (N) α(N) 可以认为是一个很小的常数。
  • 空间复杂度: O ( N ) O(N) O(N),其中 N N N 是图中的节点个数。使用数组 p a r e n t parent parent 记录每个节点的父节点,并查集使用数组记录每个节点的祖先。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值