1
+ #DFS
1
2
"""
2
- We use the `roots` to store the root of each node.
3
- For example, at index 1 value is 5, means that node1's root is node5.
3
+ For each edge (u, v), traverse the graph with a depth-first search to see if we can connect u to v. If we can, then it must be the duplicate edge.
4
+ Time Complexity: O(N^2)
5
+ Space Complexity: O(N)
6
+ """
7
+ from collections import defaultdict
4
8
5
- We use `find()` to find the node's root. In other words, the node's parent's parent's ...
6
- Every node we encounter along the way, we update their root value in `roots` to the up most root. (This technique is called path compression).
9
+ class Solution (object ):
10
+ def findRedundantConnection (self , edges ):
11
+ def dfs (u , v ):
12
+ seen = set ()
13
+ stack = []
14
+ stack .append (u )
15
+
16
+ while stack :
17
+ node = stack .pop ()
18
+ seen .add (node )
19
+
20
+ if v in G [node ]: return True
21
+
22
+ for nei in G [node ]:
23
+ if nei not in seen :
24
+ stack .append (nei )
25
+ return False
26
+
27
+ G = defaultdict (set )
28
+
29
+ for u , v in edges :
30
+ if u in G and v in G and dfs (u , v ): return u , v
31
+ G [u ].add (v )
32
+ G [v ].add (u )
7
33
8
- So, for every edge, we unify `u` and `v` them. #[1]
9
- Which means u and v and all of their parents all lead to one root.
34
+ #Disjoint Set Union
35
+ """
36
+ For Disjoint Set Union, see
37
+ https://www.youtube.com/watch?v=ID00PMy0-vE
38
+ Time Complexity: O(N)
39
+ Space Complexity: O(N)
40
+ """
41
+ class DSU (object ):
42
+ def __init__ (self ):
43
+ self .parant = range (1001 )
44
+ self .rank = [0 ]* 1001
45
+
46
+ def find (self , x ):
47
+ if self .parant [x ]!= x :
48
+ self .parant [x ] = self .find (self .parant [x ])
49
+ return self .parant [x ]
10
50
11
- If u's root (`ur`) and v's root (`vr`) are already the same before we unify them.
12
- This edge is redundant.
51
+ def union (self , x , y ):
52
+ xr , yr = self .find (x ), self .find (y )
53
+ if xr == yr :
54
+ return False
55
+ elif self .rank [xr ]> self .rank [yr ]:
56
+ self .parant [yr ] = xr
57
+ self .rank [xr ] += 1
58
+ else :
59
+ self .parant [xr ] = yr
60
+ self .rank [yr ] += 1
61
+ return True
13
62
14
- This algorithm is called Union Find, often used in undirected graph cycle detect or grouping.
15
- If you wanted to detect cycles in directed graph, you need to use Topological sort.
16
- """
17
63
class Solution (object ):
18
64
def findRedundantConnection (self , edges ):
19
- def find ( x ):
20
- if x != roots [ x ] :
21
- roots [ x ] = find ( roots [ x ])
22
- return roots [ x ]
65
+ dsu = DSU ()
66
+ for edge in edges :
67
+ if not dsu . union ( * edge ):
68
+ return edge
23
69
24
- opt = []
25
- roots = range (len (edges ))
26
-
27
- for u , v in edges :
28
- # union
29
- ur = find (u )
30
- vr = find (v )
70
+ #Disjoint Set Union without Ranking
71
+ """
72
+ Time Complexity: O(N)
73
+ Space Complexity: O(N)
74
+ """
75
+ class DSU (object ):
76
+ def __init__ (self ):
77
+ self .parant = range (1001 )
78
+
79
+ def find (self , x ):
80
+ if self .parant [x ]!= x :
81
+ self .parant [x ] = self .find (self .parant [x ])
82
+ return self .parant [x ]
31
83
32
- if ur == vr : #[2]
33
- opt = [u , v ]
34
- else :
35
- roots [vr ] = ur #[1]
84
+ def union (self , x , y ):
85
+ xr , yr = self .find (x ), self .find (y )
86
+ if xr == yr : return False
87
+ self .parant [yr ] = xr
88
+ return True
36
89
37
- return opt
90
+ class Solution (object ):
91
+ def findRedundantConnection (self , edges ):
92
+ dsu = DSU ()
93
+ for edge in edges :
94
+ if not dsu .union (* edge ):
95
+ return edge
0 commit comments