0%

leetcode每日一题-冗余连接II

冗余连接II

image1

在一棵树中,边的数量比点的数量少1,因为多了一条附加边,所以点的数量和边的数量相同,都为$N$。

树中的每个节点都有一个父节点,除了根节点没有父节点。在多了一条附加的边之后,可能有以下两种情况:

  • 附加的边指向根节点,则包括根节点在内的每个节点都有一个父节点,此时图中一定有环路;

  • 附加的边指向非根节点,则恰好有一个节点(即被附加的边指向的节点)有两个父节点,此时图中可能有环路也可能没有环路。

具体做法是,使用数组 $\textit{parent}$ 记录每个节点的父节点,初始时对于任何 $1 \le i \le N$都有 $\textit{parent}[i]=i$,另外创建并查集,初始时并查集中的每个节点都是一个连通分支,该连通分支的根节点就是该节点本身。遍历每条边的过程中,维护导致冲突的边和导致环路出现的边,由于只有一条附加的边,因此最多有一条导致冲突的边和一条导致环路出现的边。

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

  • 如果此时已经有$ \textit{parent}[v] \ne v$,说明 $v$ 有两个父节点,将当前的边$ [u,v]$ 记为导致冲突的边;

  • 否则,令 $\textit{parent}[v] = u$,然后在并查集中分别找到 $u$ 和 $v$ 的祖先(即各自的连通分支中的根节点),如果祖先相同,说明这条边导致环路出现,将当前的边$ [u,v]$ 记为导致环路出现的边,如果祖先不同,则在并查集中将 $u$ 和 $v$ 进行合并。

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

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

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

如果有导致冲突的边,记这条边为$ [u,v]$,则有两条边指向 $v$,另一条边为$ [\textit{parent}[v],v]$,需要通过判断是否有导致环路的边决定哪条边是附加的边。

  • 如果有导致环路的边,则附加的边不可能是$ [u,v]$(因为$ [u,v]$ 已经被记为导致冲突的边,不可能被记为导致环路出现的边),因此附加的边是$ [\textit{parent}[v],v]$。

  • 如果没有导致环路的边,则附加的边是后被访问到的指向 $v$ 的边,因此附加的边是$ [u,v]$。

1
class Solution {
2
    public int[] findRedundantDirectedConnection(int[][] edges) {
3
        int[] ans = new int[2];
4
        int n = edges.length;
5
        DSU dsu = new DSU(n+1);
6
        int conflict = -1;
7
        int cycle = -1;
8
        int[] parent = new int[n+1];
9
        for(int i=1; i<=n; i++){
10
            parent[i] = i;
11
        }
12
        for(int i=0; i<n; i++){
13
            int a = edges[i][0];
14
            int b = edges[i][1];
15
            if(parent[b] != b){
16
                conflict = i;
17
            }else{
18
                parent[b] = a;
19
                if(dsu.find(a) == dsu.find(b)){
20
                    cycle = i;
21
                }else{
22
                    dsu.merge(a, b);
23
                }
24
            }
25
        }
26
        if(conflict < 0){
27
            ans = edges[cycle];
28
        }else{
29
            int[] conflictEdge = edges[conflict];
30
            if(cycle >= 0){
31
                ans = new int[]{parent[conflictEdge[1]], conflictEdge[1]};
32
            }else{
33
                ans = new int[]{conflictEdge[0], conflictEdge[1]};
34
            }
35
        }
36
        return ans;
37
    }
38
}
39
40
class DSU{
41
    int[] p;
42
    int[] rank;
43
44
    public DSU(int n){
45
        p = new int[n];
46
        rank = new int[n];
47
        reset();
48
    }
49
50
    public void reset(){
51
        for(int i=0; i<p.length; i++){
52
            p[i] = i;
53
            rank[i] = 0;
54
        }
55
    }
56
57
    public int find(int x){
58
        if(p[x]==p[p[x]]){
59
            return p[x];
60
        }
61
        return p[x] = find(p[x]);
62
    }
63
64
    public void merge(int a, int b){
65
        a = find(a);
66
        b = find(b);
67
        if(a == b){
68
            return;
69
        }
70
        if(rank[a] == rank[b]){
71
            rank[a]++;
72
        }
73
        if(rank[a] < rank[b]){
74
            int tmp = a;
75
            a = b;
76
            b = tmp;
77
        }
78
        p[b] = a;
79
    }
80
}

复杂度分析

  • 时间复杂度:$O(NlogN)$。
  • 空间复杂度:$O(N)$。