Lesson 154 of 250
261. Graph Valid Tree
NeetCode: Graph Valid Tree · LeetCode: 261. Graph Valid Tree · Difficulty: Medium
Given n nodes labeled 0..n-1 and a list of undirected edges, return true if the edges form a valid tree, i.e. the graph is connected and has no cycle.
Example
n = 5, edges = [[0,1],[0,2],[0,3],[1,4]] Output: true
n = 5, edges = [[0,1],[1,2],[2,3],[1,3],[1,4]] Output: false (cycle 1-2-3)
n = 4, edges = [[0,1],[2,3]] Output: false (two components)Brute force: DFS with cycle detection and a connectivity check
A tree is a graph that is connected and has no cycle. Run a DFS from node 0 carrying each node's parent. Seeing an already visited node that is not the parent means a cycle. After the DFS, every node must have been visited, otherwise the graph is disconnected.
The adjacency list is an array of ArrayList<Integer>. The DFS uses an ArrayDeque of int[]{node, parent} pairs as an explicit stack, and -1 stands for "no parent" at the root.
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
class Solution {
public boolean validTree(int n, int[][] edges) {
if (n == 0) {
return false;
}
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) {
adj.add(new ArrayList<>());
}
for (int[] e : edges) {
adj.get(e[0]).add(e[1]);
adj.get(e[1]).add(e[0]);
}
boolean[] visited = new boolean[n];
visited[0] = true;
Deque<int[]> stack = new ArrayDeque<>();
stack.push(new int[]{0, -1});
while (!stack.isEmpty()) {
int[] top = stack.pop();
int node = top[0];
int parent = top[1];
for (int next : adj.get(node)) {
if (next == parent) {
continue;
}
if (visited[next]) {
return false;
}
visited[next] = true;
stack.push(new int[]{next, node});
}
}
for (boolean v : visited) {
if (!v) {
return false;
}
}
return true;
}
}
public class Main {
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.validTree(5, new int[][]{{0, 1}, {0, 2}, {0, 3}, {1, 4}}));
System.out.println(s.validTree(5, new int[][]{{0, 1}, {1, 2}, {2, 3}, {1, 3}, {1, 4}}));
System.out.println(s.validTree(4, new int[][]{{0, 1}, {2, 3}}));
System.out.println(s.validTree(1, new int[][]{}));
}
}true
false
false
trueThe edge back to the parent must be skipped, otherwise every edge looks like a 2-cycle, and the final connectivity check is required to reject disconnected graphs. Skipping by parent label assumes no duplicate edges. Time: O(n + E). Space: O(n + E).
Optimal: the n - 1 edge rule with union-find
A connected graph on n nodes has at least n - 1 edges, and an acyclic one has at most n - 1. So a tree has exactly n - 1 edges, and with that count either property implies the other: no cycle means connected, and connected means no cycle. So check edges.length == n - 1, then union all edges; if any union finds both endpoints already together, there is a cycle. No adjacency list is needed, and a wrong edge count is rejected immediately. The asymptotic cost matches the DFS.
Union-find (a disjoint set union, DSU) keeps each node's parent in an int[]. find returns a set's root and shortens paths as it walks. union attaches the smaller set under the larger one and returns false when both nodes already share a root.
class Dsu {
private final int[] parent;
private final int[] size;
Dsu(int n) {
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
boolean union(int a, int b) {
int ra = find(a);
int rb = find(b);
if (ra == rb) {
return false;
}
if (size[ra] < size[rb]) {
int tmp = ra;
ra = rb;
rb = tmp;
}
parent[rb] = ra;
size[ra] += size[rb];
return true;
}
}
class Solution {
public boolean validTree(int n, int[][] edges) {
if (n == 0 || edges.length != n - 1) {
return false;
}
Dsu dsu = new Dsu(n);
for (int[] e : edges) {
if (!dsu.union(e[0], e[1])) {
return false;
}
}
return true;
}
}
public class Main {
public static void main(String[] args) {
Solution s = new Solution();
System.out.println(s.validTree(5, new int[][]{{0, 1}, {0, 2}, {0, 3}, {1, 4}}));
System.out.println(s.validTree(5, new int[][]{{0, 1}, {1, 2}, {2, 3}, {1, 3}, {1, 4}}));
System.out.println(s.validTree(4, new int[][]{{0, 1}, {2, 3}}));
System.out.println(s.validTree(1, new int[][]{}));
}
}true
false
false
trueThe n == 0 check returns first so the comparison with n - 1 only runs for n >= 1. Time: O(n * alpha(n)). Space: O(n).
