Graph Valid Tree

MediumGraphDFSBFSUnion FindCycle Detection
Category: Graphs
Companies that ask this question:
GoogleFacebookAmazonZenefitsMicrosoft

Approach

Graph Valid Tree

Problem Statement

You have a graph of n nodes labeled from 0 to n - 1. You are given an integer n and a list of edges where edges[i] = [ai, bi] indicates that there is an undirected edge between nodes ai and bi in the graph.

Return true if the edges of the given graph make up a valid tree, and false otherwise.

Examples

Example 1:

Input: n = 5, edges = [[0,1],[0,2],[0,3],[1,4]] Output: true

Example 2:

Input: n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] Output: false Explanation: There is a cycle: 1 → 2 → 3 → 1

Approach: DFS with Cycle Detection

A valid tree must satisfy three conditions:

  1. Exactly n-1 edges (a tree with n nodes has exactly n-1 edges)
  2. Connected (all nodes are reachable from any node)
  3. No cycles (acyclic)

Algorithm Steps:

  1. Quick Check: If edges.length ≠ n - 1, return false (not a tree)
  2. Build Graph: Create adjacency list from edges
  3. DFS Traversal:
    • Track visited nodes
    • Track parent to avoid false cycle detection (undirected graph)
    • If we visit an already-visited node (not parent), we found a cycle
  4. Connectivity Check: After DFS, verify all nodes were visited

Why This Works:

  • Edge Count: Trees have exactly n-1 edges (fundamental property)
  • Cycle Detection: If we encounter a visited node that's not our parent, it's a cycle
  • Connectivity: DFS from one node should reach all nodes in a tree

Time Complexity: O(V + E)

  • V = number of nodes
  • E = number of edges
  • Visit each node and edge once

Space Complexity: O(V + E)

  • Adjacency list: O(E)
  • Visited set: O(V)
  • DFS recursion stack: O(V)

Alternative Approaches

Union-Find

  • Unite nodes as we process edges
  • If uniting creates a cycle, not a tree
  • Check if all nodes are in same component

BFS

  • Similar to DFS but uses queue
  • Track parent and visited nodes
  • Check connectivity after BFS

Key Points

  • Trees are connected acyclic graphs
  • For undirected graphs, track parent to avoid false cycles
  • Quick edge count check eliminates many invalid cases
  • A tree with n nodes has exactly n-1 edges
  • All trees are connected by definition

Solution

java
1class Solution {
2    public boolean validTree(int n, int[][] edges) {
3        // Quick check: tree must have exactly n-1 edges
4        if (edges.length != n - 1) {
5            return false;
6        }
7
8        // Build adjacency list
9        List<List<Integer>> graph = new ArrayList<>();
10        for (int i = 0; i < n; i++) {
11            graph.add(new ArrayList<>());
12        }
13        for (int[] edge : edges) {
14            graph.get(edge[0]).add(edge[1]);
15            graph.get(edge[1]).add(edge[0]);
16        }
17
18        // DFS to check for cycles and connectivity
19        boolean[] visited = new boolean[n];
20        if (!dfs(0, -1, graph, visited)) {
21            return false; // Cycle detected
22        }
23
24        // Check if all nodes were visited (connected)
25        for (boolean v : visited) {
26            if (!v) return false;
27        }
28
29        return true;
30    }
31
32    private boolean dfs(int node, int parent, List<List<Integer>> graph, boolean[] visited) {
33        visited[node] = true;
34
35        for (int neighbor : graph.get(node)) {
36            if (!visited[neighbor]) {
37                if (!dfs(neighbor, node, graph, visited)) {
38                    return false;
39                }
40            } else if (neighbor != parent) {
41                // Found cycle: visited node that's not parent
42                return false;
43            }
44        }
45
46        return true;
47    }
48}
49
50/**
51 * Solution using Union-Find (Disjoint Set Union)
52 */
53class SolutionUnionFind {
54    public boolean validTree(int n, int[][] edges) {
55        // Tree must have exactly n-1 edges
56        if (edges.length != n - 1) {
57            return false;
58        }
59
60        UnionFind uf = new UnionFind(n);
61
62        for (int[] edge : edges) {
63            if (!uf.union(edge[0], edge[1])) {
64                return false; // Cycle detected
65            }
66        }
67
68        return true; // No cycles and correct edge count means valid tree
69    }
70
71    class UnionFind {
72        private int[] parent;
73        private int[] rank;
74
75        public UnionFind(int size) {
76            parent = new int[size];
77            rank = new int[size];
78            for (int i = 0; i < size; i++) {
79                parent[i] = i;
80                rank[i] = 1;
81            }
82        }
83
84        public int find(int x) {
85            if (parent[x] != x) {
86                parent[x] = find(parent[x]); // Path compression
87            }
88            return parent[x];
89        }
90
91        public boolean union(int x, int y) {
92            int rootX = find(x);
93            int rootY = find(y);
94
95            if (rootX == rootY) {
96                return false; // Already in same component (cycle)
97            }
98
99            // Union by rank
100            if (rank[rootX] < rank[rootY]) {
101                parent[rootX] = rootY;
102            } else if (rank[rootX] > rank[rootY]) {
103                parent[rootY] = rootX;
104            } else {
105                parent[rootY] = rootX;
106                rank[rootX]++;
107            }
108
109            return true;
110        }
111    }
112}
113
114/**
115 * Solution using BFS
116 */
117class SolutionBFS {
118    public boolean validTree(int n, int[][] edges) {
119        if (edges.length != n - 1) {
120            return false;
121        }
122
123        // Build graph
124        List<List<Integer>> graph = new ArrayList<>();
125        for (int i = 0; i < n; i++) {
126            graph.add(new ArrayList<>());
127        }
128        for (int[] edge : edges) {
129            graph.get(edge[0]).add(edge[1]);
130            graph.get(edge[1]).add(edge[0]);
131        }
132
133        // BFS
134        boolean[] visited = new boolean[n];
135        Queue<int[]> queue = new LinkedList<>();
136        queue.offer(new int[]{0, -1}); // [node, parent]
137        visited[0] = true;
138
139        while (!queue.isEmpty()) {
140            int[] curr = queue.poll();
141            int node = curr[0];
142            int parent = curr[1];
143
144            for (int neighbor : graph.get(node)) {
145                if (!visited[neighbor]) {
146                    visited[neighbor] = true;
147                    queue.offer(new int[]{neighbor, node});
148                } else if (neighbor != parent) {
149                    return false; // Cycle
150                }
151            }
152        }
153
154        // Check connectivity
155        for (boolean v : visited) {
156            if (!v) return false;
157        }
158
159        return true;
160    }
161}
162
163/**
164 * Iterative DFS solution
165 */
166class SolutionIterative {
167    public boolean validTree(int n, int[][] edges) {
168        if (edges.length != n - 1) return false;
169
170        List<Set<Integer>> graph = new ArrayList<>();
171        for (int i = 0; i < n; i++) {
172            graph.add(new HashSet<>());
173        }
174        for (int[] edge : edges) {
175            graph.get(edge[0]).add(edge[1]);
176            graph.get(edge[1]).add(edge[0]);
177        }
178
179        Set<Integer> visited = new HashSet<>();
180        Stack<Integer> stack = new Stack<>();
181        stack.push(0);
182
183        while (!stack.isEmpty()) {
184            int node = stack.pop();
185            if (visited.contains(node)) continue;
186            visited.add(node);
187
188            for (int neighbor : graph.get(node)) {
189                graph.get(neighbor).remove(node); // Remove reverse edge
190                stack.push(neighbor);
191            }
192        }
193
194        return visited.size() == n;
195    }
196}
197
Loading visualizer...