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