Graph Valid Tree
You have a graph of n nodes labeled from 0 to n - 1. You are given an integer n and edges where edgesi = [ai, bi] indicates an undirected edge between nodes ai and bi. Return true if the edges form a valid tree , or false otherwise. A valid tree has exactly n - 1 edges and no cycle. Check the edge count first, then union every edge — any edge whose endpoints already share a root closes a cycle.
Constraints
- 1 ≤ n ≤ 2000
- 0 ≤ edges.length ≤ 5000
- edgesi.length == 2
- 0 ≤ ai, bi < n
- ai != bi
Example
n = 5, edges = [[0,1],[0,2],[0,3],[1,4]]trueExplanation Four edges connect all five nodes with no cycle — this is a valid tree.
In plain terms
- Valid tree
- A connected graph with no cycles — every node reachable from every other, with no redundant edge.
Check the edge count, then union edges watching for a cycle
edges.length = 4 = n - 1, so the count check passes. Union edge [0, 1].
What happens in this step
edge [0, 1] find(0) → 0 find(1) → 1 roots differ → union: parent[0] = 1 parent array: [0, 1, 2, 3, 4] → [1, 1, 2, 3, 4] Nodes 0 and 1 merge, with root 1 now representing both.
Steps to visualize
- If edges.length is not exactly n - 1, it cannot be a tree — return false immediately.
- Otherwise give every node its own group.
- For each edge, compare find on both endpoints; if they already match, a cycle exists — return false.
- Otherwise union them and continue.
- If every edge unions cleanly, the graph is a valid tree.
Walk through the code
Same walkthrough, now with the code. Press Next to move one step and watch which lines run.
edges.length = 4 = n - 1, so the count check passes. Union edge [0, 1].
What happens in this step
edge [0, 1] find(0) → 0 find(1) → 1 roots differ → union: parent[0] = 1 parent array: [0, 1, 2, 3, 4] → [1, 1, 2, 3, 4] Nodes 0 and 1 merge, with root 1 now representing both.
Solution
function validTree(n, edges) {
if (edges.length !== n - 1) {
return false;
}
const parent = Array.from({ length: n }, (_, i) => i);
function find(x) {
while (parent[x] !== x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
for (const [a, b] of edges) {
const rootA = find(a);
const rootB = find(b);
if (rootA === rootB) {
return false;
}
parent[rootA] = rootB;
}
return true;
}- Time
- O(n · α(n))
- Space
- O(n)
Test cases
| Input | Expected | Covers |
|---|---|---|
n = 5, edges = [[0,1],[0,2],[0,3],[1,4]] | true | example from the docstring |
n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] | false | too many edges for the number of nodes |
n = 1, edges = [] | true | a single node with no edges is trivially a valid tree |
n = 4, edges = [[0,1],[2,3]] | false | not enough edges to connect every node |
n = 4, edges = [[0,1],[1,2],[2,0]] | false | edge count matches n - 1, but a cycle exists and one node is isolated |
n = 4, edges = [[0,1],[1,2],[2,3]] | true | a simple chain with no branching is still a valid tree |