1
+ package com .leetcode .graphs ;
2
+
3
+ import java .util .ArrayList ;
4
+ import java .util .List ;
5
+
6
+ import static org .junit .jupiter .api .Assertions .assertFalse ;
7
+ import static org .junit .jupiter .api .Assertions .assertTrue ;
8
+
9
+ /**
10
+ * Level: Medium
11
+ * Problem Link: https://leetcode.com/problems/graph-valid-tree/
12
+ * Problem Description:
13
+ * Given n nodes labeled from 0 to n-1 and a list of undirected edges (each edge is a pair of nodes), write a function
14
+ * to check whether these edges make up a valid tree.
15
+ * <p>
16
+ * Example 1:
17
+ * Input: n = 5, and edges = [[0,1], [0,2], [0,3], [1,4]]
18
+ * Output: true
19
+ * <p>
20
+ * Example 2:
21
+ * Input: n = 5, and edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
22
+ * Output: false
23
+ * <p>
24
+ * Note: you can assume that no duplicate edges will appear in edges. Since all edges are undirected, [0,1] is the
25
+ * same as [1,0] and thus will not appear together in edges.
26
+ *
27
+ * @author rampatra
28
+ * @since 2019-08-05
29
+ */
30
+ public class GraphValidTree {
31
+
32
+ /**
33
+ *
34
+ * @param n
35
+ * @param edges
36
+ * @return
37
+ */
38
+ public static boolean isValidTree (int n , int [][] edges ) {
39
+ List <List <Integer >> adjacencyList = new ArrayList <>(n );
40
+
41
+ for (int i = 0 ; i < n ; i ++) {
42
+ adjacencyList .add (new ArrayList <>());
43
+ }
44
+
45
+ for (int i = 0 ; i < edges .length ; i ++) {
46
+ adjacencyList .get (edges [i ][0 ]).add (edges [i ][1 ]);
47
+ }
48
+
49
+ boolean [] visited = new boolean [n ];
50
+
51
+ if (hasCycle (adjacencyList , 0 , -1 , visited )) {
52
+ return false ;
53
+ }
54
+
55
+ for (int i = 0 ; i < n ; i ++) {
56
+ if (!visited [i ]) {
57
+ return false ;
58
+ }
59
+ }
60
+
61
+ return true ;
62
+ }
63
+
64
+ private static boolean hasCycle (List <List <Integer >> adjacencyList , int node1 , int exclude , boolean [] visited ) {
65
+ visited [node1 ] = true ;
66
+
67
+ for (int i = 0 ; i < adjacencyList .get (node1 ).size (); i ++) {
68
+ int node2 = adjacencyList .get (node1 ).get (i );
69
+
70
+ if ((visited [node2 ] && exclude != node2 ) || (!visited [node2 ] && hasCycle (adjacencyList , node2 , node1 , visited ))) {
71
+ return true ;
72
+ }
73
+ }
74
+
75
+ return false ;
76
+ }
77
+
78
+
79
+ /**
80
+ * Union-find algorithm: We keep all connected nodes in one set in the union operation and in find operation we
81
+ * check whether two nodes belong to the same set. If yes then there's a cycle and if not then no cycle.
82
+ *
83
+ * Good articles on union-find:
84
+ * - https://www.hackerearth.com/practice/notes/disjoint-set-union-union-find/
85
+ * - https://www.youtube.com/watch?v=wU6udHRIkcc
86
+ *
87
+ * @param n
88
+ * @param edges
89
+ * @return
90
+ */
91
+ public static boolean isValidTreeUsingUnionFind (int n , int [][] edges ) {
92
+ int [] roots = new int [n ];
93
+
94
+ for (int i = 0 ; i < n ; i ++) {
95
+ roots [i ] = i ;
96
+ }
97
+
98
+ for (int i = 0 ; i < edges .length ; i ++) {
99
+ // find operation
100
+ if (roots [edges [i ][0 ]] == roots [edges [i ][1 ]]) {
101
+ return false ;
102
+ }
103
+ // union operation
104
+ roots [edges [i ][1 ]] = findRoot (roots , roots [edges [i ][0 ]]); // note: we can optimize this even further by
105
+ // considering size of each side and then join the side with smaller size to the one with a larger size (weighted union).
106
+ // We can use another array called size to keep count of the size or we can use the same root array with
107
+ // negative values, i.e, negative resembles that the node is pointing to itself and the number will represent
108
+ // the size. For example, roots = [-2, -1, -1, 0] means that node 3 is pointing to node 0 and node 0 is pointing
109
+ // to itself and is has 2 nodes under it including itself.
110
+ }
111
+
112
+ return edges .length == n - 1 ;
113
+ }
114
+
115
+ private static int findRoot (int [] roots , int node ) {
116
+ while (roots [node ] != node ) {
117
+ node = roots [node ];
118
+ }
119
+ return node ;
120
+ }
121
+
122
+ public static void main (String [] args ) {
123
+ assertTrue (isValidTree (5 , new int [][]{{0 , 1 }, {0 , 2 }, {0 , 3 }, {1 , 4 }}));
124
+ assertFalse (isValidTree (5 , new int [][]{{0 , 1 }, {1 , 2 }, {2 , 3 }, {1 , 3 }, {1 , 4 }}));
125
+ assertFalse (isValidTree (3 , new int [][]{{0 , 1 }, {1 , 2 }, {2 , 0 }}));
126
+
127
+ assertTrue (isValidTreeUsingUnionFind (5 , new int [][]{{0 , 1 }, {0 , 2 }, {0 , 3 }, {1 , 4 }}));
128
+ assertFalse (isValidTreeUsingUnionFind (5 , new int [][]{{0 , 1 }, {1 , 2 }, {2 , 3 }, {1 , 3 }, {1 , 4 }}));
129
+ assertFalse (isValidTreeUsingUnionFind (3 , new int [][]{{0 , 1 }, {1 , 2 }, {2 , 0 }}));
130
+ }
131
+ }
0 commit comments