Depth-First Search
Breadth-First Search
Union Find
Graph
Description
In this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) for which all other nodes are descendants of this node, plus every node has exactly one parent, except for the root node which has no parents.
The given input is a directed graph that started as a rooted tree with n
nodes (with distinct values from 1
to n
), with one additional directed edge added. The added edge has two different vertices chosen from 1
to n
, and was not an edge that already existed.
The resulting graph is given as a 2D-array of edges
. Each element of edges
is a pair [ui , vi ]
that represents a directed edge connecting nodes ui
and vi
, where ui
is a parent of child vi
.
Return an edge that can be removed so that the resulting graph is a rooted tree of n
nodes . If there are multiple answers, return the answer that occurs last in the given 2D-array.
Example 1:
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]
Example 2:
Input: edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]
Output: [4,1]
Constraints:
n == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ui , vi <= n
ui != vi
Solutions
Solution 1: Union-Find
According to the problem description, for a rooted tree, the in-degree of the root node is $0$, and the in-degree of other nodes is $1$. After adding an edge to the tree, there can be the following three scenarios:
The added edge points to a non-root node, and the in-degree of that node becomes $2$. In this case, there is no directed cycle in the graph:
The added edge points to a non-root node, and the in-degree of that node becomes $2$. In this case, there is a directed cycle in the graph:
The added edge points to the root node, and the in-degree of the root node becomes $1$. In this case, there is a directed cycle in the graph, but there are no nodes with an in-degree of $2$.
Therefore, we first calculate the in-degree of each node. If there exists a node with an in-degree of $2$, we identify the two edges corresponding to that node, denoted as $\textit{dup}[0]$ and $\textit{dup}[1]$. If deleting $\textit{dup}[1]$ results in the remaining edges not forming a tree, then $\textit{dup}[0]$ is the edge that needs to be deleted; otherwise, $\textit{dup}[1]$ is the edge that needs to be deleted.
If there are no nodes with an in-degree of $2$, we traverse the array $\textit{edges}$. For each edge $(u, v)$, we use the union-find data structure to maintain connectivity between nodes. If $u$ and $v$ are already connected, it indicates that there is a directed cycle in the graph, and the current edge is the one that needs to be deleted.
The time complexity is $O(n \log n)$, and the space complexity is $O(n)$, where $n$ is the number of edges.
Python3 Java C++ Go TypeScript JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 class Solution :
def findRedundantDirectedConnection ( self , edges : List [ List [ int ]]) -> List [ int ]:
def find ( x : int ) -> int :
if p [ x ] != x :
p [ x ] = find ( p [ x ])
return p [ x ]
n = len ( edges )
ind = [ 0 ] * n
for _ , v in edges :
ind [ v - 1 ] += 1
dup = [ i for i , ( _ , v ) in enumerate ( edges ) if ind [ v - 1 ] == 2 ]
p = list ( range ( n ))
if dup :
for i , ( u , v ) in enumerate ( edges ):
if i == dup [ 1 ]:
continue
pu , pv = find ( u - 1 ), find ( v - 1 )
if pu == pv :
return edges [ dup [ 0 ]]
p [ pu ] = pv
return edges [ dup [ 1 ]]
for i , ( u , v ) in enumerate ( edges ):
pu , pv = find ( u - 1 ), find ( v - 1 )
if pu == pv :
return edges [ i ]
p [ pu ] = pv
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 class Solution {
private int [] p ;
public int [] findRedundantDirectedConnection ( int [][] edges ) {
int n = edges . length ;
int [] ind = new int [ n ] ;
for ( var e : edges ) {
++ ind [ e [ 1 ] - 1 ] ;
}
List < Integer > dup = new ArrayList <> ();
p = new int [ n ] ;
for ( int i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] == 2 ) {
dup . add ( i );
}
p [ i ] = i ;
}
if ( ! dup . isEmpty ()) {
for ( int i = 0 ; i < n ; ++ i ) {
if ( i == dup . get ( 1 )) {
continue ;
}
int pu = find ( edges [ i ][ 0 ] - 1 );
int pv = find ( edges [ i ][ 1 ] - 1 );
if ( pu == pv ) {
return edges [ dup . get ( 0 ) ] ;
}
p [ pu ] = pv ;
}
return edges [ dup . get ( 1 ) ] ;
}
for ( int i = 0 ;; ++ i ) {
int pu = find ( edges [ i ][ 0 ] - 1 );
int pv = find ( edges [ i ][ 1 ] - 1 );
if ( pu == pv ) {
return edges [ i ] ;
}
p [ pu ] = pv ;
}
}
private int find ( int x ) {
if ( p [ x ] != x ) {
p [ x ] = find ( p [ x ] );
}
return p [ x ] ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 class Solution {
public :
vector < int > findRedundantDirectedConnection ( vector < vector < int >>& edges ) {
int n = edges . size ();
vector < int > ind ( n );
for ( const auto & e : edges ) {
++ ind [ e [ 1 ] - 1 ];
}
vector < int > dup ;
for ( int i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] == 2 ) {
dup . push_back ( i );
}
}
vector < int > p ( n );
iota ( p . begin (), p . end (), 0 );
function < int ( int ) > find = [ & ]( int x ) {
return x == p [ x ] ? x : p [ x ] = find ( p [ x ]);
};
if ( ! dup . empty ()) {
for ( int i = 0 ; i < n ; ++ i ) {
if ( i == dup [ 1 ]) {
continue ;
}
int pu = find ( edges [ i ][ 0 ] - 1 );
int pv = find ( edges [ i ][ 1 ] - 1 );
if ( pu == pv ) {
return edges [ dup [ 0 ]];
}
p [ pu ] = pv ;
}
return edges [ dup [ 1 ]];
}
for ( int i = 0 ;; ++ i ) {
int pu = find ( edges [ i ][ 0 ] - 1 );
int pv = find ( edges [ i ][ 1 ] - 1 );
if ( pu == pv ) {
return edges [ i ];
}
p [ pu ] = pv ;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 func findRedundantDirectedConnection ( edges [][] int ) [] int {
n := len ( edges )
ind := make ([] int , n )
for _ , e := range edges {
ind [ e [ 1 ] - 1 ] ++
}
dup := [] int {}
for i , e := range edges {
if ind [ e [ 1 ] - 1 ] == 2 {
dup = append ( dup , i )
}
}
p := make ([] int , n )
for i := range p {
p [ i ] = i
}
var find func ( int ) int
find = func ( x int ) int {
if p [ x ] != x {
p [ x ] = find ( p [ x ])
}
return p [ x ]
}
if len ( dup ) > 0 {
for i , e := range edges {
if i == dup [ 1 ] {
continue
}
pu , pv := find ( e [ 0 ] - 1 ), find ( e [ 1 ] - 1 )
if pu == pv {
return edges [ dup [ 0 ]]
}
p [ pu ] = pv
}
return edges [ dup [ 1 ]]
}
for _ , e := range edges {
pu , pv := find ( e [ 0 ] - 1 ), find ( e [ 1 ] - 1 )
if pu == pv {
return e
}
p [ pu ] = pv
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40 function findRedundantDirectedConnection ( edges : number [][]) : number [] {
const n = edges . length ;
const ind : number [] = Array ( n ). fill ( 0 );
for ( const [ _ , v ] of edges ) {
++ ind [ v - 1 ];
}
const dup : number [] = [];
for ( let i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] === 2 ) {
dup . push ( i );
}
}
const p : number [] = Array . from ({ length : n }, ( _ , i ) => i );
const find = ( x : number ) : number => {
if ( p [ x ] !== x ) {
p [ x ] = find ( p [ x ]);
}
return p [ x ];
};
if ( dup . length ) {
for ( let i = 0 ; i < n ; ++ i ) {
if ( i === dup [ 1 ]) {
continue ;
}
const [ pu , pv ] = [ find ( edges [ i ][ 0 ] - 1 ), find ( edges [ i ][ 1 ] - 1 )];
if ( pu === pv ) {
return edges [ dup [ 0 ]];
}
p [ pu ] = pv ;
}
return edges [ dup [ 1 ]];
}
for ( let i = 0 ; ; ++ i ) {
const [ pu , pv ] = [ find ( edges [ i ][ 0 ] - 1 ), find ( edges [ i ][ 1 ] - 1 )];
if ( pu === pv ) {
return edges [ i ];
}
p [ pu ] = pv ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 /**
* @param {number[][]} edges
* @return {number[]}
*/
var findRedundantDirectedConnection = function ( edges ) {
const n = edges . length ;
const ind = Array ( n ). fill ( 0 );
for ( const [ _ , v ] of edges ) {
++ ind [ v - 1 ];
}
const dup = [];
for ( let i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] === 2 ) {
dup . push ( i );
}
}
const p = Array . from ({ length : n }, ( _ , i ) => i );
const find = x => {
if ( p [ x ] !== x ) {
p [ x ] = find ( p [ x ]);
}
return p [ x ];
};
if ( dup . length ) {
for ( let i = 0 ; i < n ; ++ i ) {
if ( i === dup [ 1 ]) {
continue ;
}
const [ pu , pv ] = [ find ( edges [ i ][ 0 ] - 1 ), find ( edges [ i ][ 1 ] - 1 )];
if ( pu === pv ) {
return edges [ dup [ 0 ]];
}
p [ pu ] = pv ;
}
return edges [ dup [ 1 ]];
}
for ( let i = 0 ; ; ++ i ) {
const [ pu , pv ] = [ find ( edges [ i ][ 0 ] - 1 ), find ( edges [ i ][ 1 ] - 1 )];
if ( pu === pv ) {
return edges [ i ];
}
p [ pu ] = pv ;
}
};
Solution 2: Union-Find (Template Approach)
Here is a template approach using Union-Find for your reference.
The time complexity is $O(n \alpha(n))$, and the space complexity is $O(n)$. Here, $n$ is the number of edges, and $\alpha(n)$ is the inverse Ackermann function, which can be considered a very small constant.
Python3 Java C++ Go TypeScript JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 class UnionFind :
__slots__ = "p" , "size"
def __init__ ( self , n : int ):
self . p : List [ int ] = list ( range ( n ))
self . size : List [ int ] = [ 1 ] * n
def find ( self , x : int ) -> int :
if self . p [ x ] != x :
self . p [ x ] = self . find ( self . p [ x ])
return self . p [ x ]
def union ( self , a : int , b : int ) -> bool :
pa , pb = self . find ( a ), self . find ( b )
if pa == pb :
return False
if self . size [ pa ] > self . size [ pb ]:
self . p [ pb ] = pa
self . size [ pa ] += self . size [ pb ]
else :
self . p [ pa ] = pb
self . size [ pb ] += self . size [ pa ]
return True
class Solution :
def findRedundantDirectedConnection ( self , edges : List [ List [ int ]]) -> List [ int ]:
n = len ( edges )
ind = [ 0 ] * n
for _ , v in edges :
ind [ v - 1 ] += 1
dup = [ i for i , ( _ , v ) in enumerate ( edges ) if ind [ v - 1 ] == 2 ]
uf = UnionFind ( n )
if dup :
for i , ( u , v ) in enumerate ( edges ):
if i == dup [ 1 ]:
continue
if not uf . union ( u - 1 , v - 1 ):
return edges [ dup [ 0 ]]
return edges [ dup [ 1 ]]
for i , ( u , v ) in enumerate ( edges ):
if not uf . union ( u - 1 , v - 1 ):
return edges [ i ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 class UnionFind {
private final int [] p ;
private final int [] size ;
public UnionFind ( int n ) {
p = new int [ n ] ;
size = new int [ n ] ;
for ( int i = 0 ; i < n ; ++ i ) {
p [ i ] = i ;
size [ i ] = 1 ;
}
}
public int find ( int x ) {
if ( p [ x ] != x ) {
p [ x ] = find ( p [ x ] );
}
return p [ x ] ;
}
public boolean union ( int a , int b ) {
int pa = find ( a ), pb = find ( b );
if ( pa == pb ) {
return false ;
}
if ( size [ pa ] > size [ pb ] ) {
p [ pb ] = pa ;
size [ pa ] += size [ pb ] ;
} else {
p [ pa ] = pb ;
size [ pb ] += size [ pa ] ;
}
return true ;
}
}
class Solution {
public int [] findRedundantDirectedConnection ( int [][] edges ) {
int n = edges . length ;
int [] ind = new int [ n ] ;
for ( var e : edges ) {
++ ind [ e [ 1 ] - 1 ] ;
}
List < Integer > dup = new ArrayList <> ();
for ( int i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] == 2 ) {
dup . add ( i );
}
}
UnionFind uf = new UnionFind ( n );
if ( ! dup . isEmpty ()) {
for ( int i = 0 ; i < n ; ++ i ) {
if ( i == dup . get ( 1 )) {
continue ;
}
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ dup . get ( 0 ) ] ;
}
}
return edges [ dup . get ( 1 ) ] ;
}
for ( int i = 0 ;; ++ i ) {
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ i ] ;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 class UnionFind {
public :
UnionFind ( int n ) {
p = vector < int > ( n );
size = vector < int > ( n , 1 );
iota ( p . begin (), p . end (), 0 );
}
bool unite ( int a , int b ) {
int pa = find ( a ), pb = find ( b );
if ( pa == pb ) {
return false ;
}
if ( size [ pa ] > size [ pb ]) {
p [ pb ] = pa ;
size [ pa ] += size [ pb ];
} else {
p [ pa ] = pb ;
size [ pb ] += size [ pa ];
}
return true ;
}
int find ( int x ) {
if ( p [ x ] != x ) {
p [ x ] = find ( p [ x ]);
}
return p [ x ];
}
private :
vector < int > p , size ;
};
class Solution {
public :
vector < int > findRedundantDirectedConnection ( vector < vector < int >>& edges ) {
int n = edges . size ();
vector < int > ind ( n );
for ( const auto & e : edges ) {
++ ind [ e [ 1 ] - 1 ];
}
vector < int > dup ;
for ( int i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] == 2 ) {
dup . push_back ( i );
}
}
UnionFind uf ( n );
if ( ! dup . empty ()) {
for ( int i = 0 ; i < n ; ++ i ) {
if ( i == dup [ 1 ]) {
continue ;
}
if ( ! uf . unite ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ dup [ 0 ]];
}
}
return edges [ dup [ 1 ]];
}
for ( int i = 0 ;; ++ i ) {
if ( ! uf . unite ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ i ];
}
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 type unionFind struct {
p , size [] int
}
func newUnionFind ( n int ) * unionFind {
p := make ([] int , n )
size := make ([] int , n )
for i := range p {
p [ i ] = i
size [ i ] = 1
}
return & unionFind { p , size }
}
func ( uf * unionFind ) find ( x int ) int {
if uf . p [ x ] != x {
uf . p [ x ] = uf . find ( uf . p [ x ])
}
return uf . p [ x ]
}
func ( uf * unionFind ) union ( a , b int ) bool {
pa , pb := uf . find ( a ), uf . find ( b )
if pa == pb {
return false
}
if uf . size [ pa ] > uf . size [ pb ] {
uf . p [ pb ] = pa
uf . size [ pa ] += uf . size [ pb ]
} else {
uf . p [ pa ] = pb
uf . size [ pb ] += uf . size [ pa ]
}
return true
}
func findRedundantDirectedConnection ( edges [][] int ) [] int {
n := len ( edges )
ind := make ([] int , n )
for _ , e := range edges {
ind [ e [ 1 ] - 1 ] ++
}
dup := [] int {}
for i , e := range edges {
if ind [ e [ 1 ] - 1 ] == 2 {
dup = append ( dup , i )
}
}
uf := newUnionFind ( n )
if len ( dup ) > 0 {
for i , e := range edges {
if i == dup [ 1 ] {
continue
}
if ! uf . union ( e [ 0 ] - 1 , e [ 1 ] - 1 ) {
return edges [ dup [ 0 ]]
}
}
return edges [ dup [ 1 ]]
}
for _ , e := range edges {
if ! uf . union ( e [ 0 ] - 1 , e [ 1 ] - 1 ) {
return e
}
}
return nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 class UnionFind {
p : number [];
size : number [];
constructor ( n : number ) {
this . p = Array . from ({ length : n }, ( _ , i ) => i );
this . size = Array ( n ). fill ( 1 );
}
find ( x : number ) : number {
if ( this . p [ x ] !== x ) {
this . p [ x ] = this . find ( this . p [ x ]);
}
return this . p [ x ];
}
union ( a : number , b : number ) : boolean {
const [ pa , pb ] = [ this . find ( a ), this . find ( b )];
if ( pa === pb ) {
return false ;
}
if ( this . size [ pa ] > this . size [ pb ]) {
this . p [ pb ] = pa ;
this . size [ pa ] += this . size [ pb ];
} else {
this . p [ pa ] = pb ;
this . size [ pb ] += this . size [ pa ];
}
return true ;
}
}
function findRedundantDirectedConnection ( edges : number [][]) : number [] {
const n = edges . length ;
const ind : number [] = Array ( n ). fill ( 0 );
for ( const [ _ , v ] of edges ) {
++ ind [ v - 1 ];
}
const dup : number [] = [];
for ( let i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] === 2 ) {
dup . push ( i );
}
}
const uf = new UnionFind ( n );
if ( dup . length ) {
for ( let i = 0 ; i < n ; ++ i ) {
if ( i === dup [ 1 ]) {
continue ;
}
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ dup [ 0 ]];
}
}
return edges [ dup [ 1 ]];
}
for ( let i = 0 ; ; ++ i ) {
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ i ];
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 class UnionFind {
constructor ( n ) {
this . p = Array . from ({ length : n }, ( _ , i ) => i );
this . size = Array ( n ). fill ( 1 );
}
find ( x ) {
if ( this . p [ x ] !== x ) {
this . p [ x ] = this . find ( this . p [ x ]);
}
return this . p [ x ];
}
union ( a , b ) {
const pa = this . find ( a );
const pb = this . find ( b );
if ( pa === pb ) {
return false ;
}
if ( this . size [ pa ] > this . size [ pb ]) {
this . p [ pb ] = pa ;
this . size [ pa ] += this . size [ pb ];
} else {
this . p [ pa ] = pb ;
this . size [ pb ] += this . size [ pa ];
}
return true ;
}
}
/**
* @param {number[][]} edges
* @return {number[]}
*/
var findRedundantDirectedConnection = function ( edges ) {
const n = edges . length ;
const ind = Array ( n ). fill ( 0 );
for ( const [ _ , v ] of edges ) {
++ ind [ v - 1 ];
}
const dup = [];
for ( let i = 0 ; i < n ; ++ i ) {
if ( ind [ edges [ i ][ 1 ] - 1 ] === 2 ) {
dup . push ( i );
}
}
const uf = new UnionFind ( n );
if ( dup . length ) {
for ( let i = 0 ; i < n ; ++ i ) {
if ( i === dup [ 1 ]) {
continue ;
}
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ dup [ 0 ]];
}
}
return edges [ dup [ 1 ]];
}
for ( let i = 0 ; ; ++ i ) {
if ( ! uf . union ( edges [ i ][ 0 ] - 1 , edges [ i ][ 1 ] - 1 )) {
return edges [ i ];
}
}
};
GitHub