Problem
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 to check whether these edges make up a valid tree.
Examples
Example 1:
Input: n = 5, and edges = [[0,1], [0,2], [0,3], [1,4]]
Output: true
Example 2:
Input: n = 5, and edges = [[0,1], [1,2], [2,3], [1,3], [1,4]]
Output: false
Note: you can assume that no duplicate edges will appear in edges
. Since all edges are undirected, [0,1]
is the same as [1,0]
and thus will not appear together in edges
.
Solution
A tree is a special undirected graph. It satisfies two properties
- It is connected
- It has no cycle.
This problem can be converted to finding a cycle in a graph. It can be solved by using DFS (Recursion) or BFS (Queue).
Method 1 - DFS
In an undirected graph, visiting a child node often includes revisiting its parent, leading to a trivial cycle rather than a genuine one. This can be avoided by passing a parent
state in the DFS call to distinguish valid backtracking. Additionally, if the number of visited nodes equals the total nodes in the graph, it ensures the graph is connected.
Approach
- Pre-check: If the number of edges is not
n-1
, returnfalse
immediately because a valid tree must have exactlyn-1
edges. - Graph Representation:
- Use an adjacency list to represent the graph.
- DFS/BFS for Connectivity:
- Starting from a node, traverse the graph (using DFS or BFS).
- Track visited nodes to ensure no cycles.
- After traversal, check if all nodes were visited (to confirm connectivity).
Code
Java
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
if len(edges) != n - 1:
return False
# Constructing adjacency list
graph = {i: [] for i in range(n)}
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
def dfs(node: int, parent: int) -> bool:
if node in visited:
return False
visited.add(node)
for nbr in graph[node]:
if nbr != parent:
if not dfs(nbr, node):
return False
return True
# Check for cycles
if not dfs(0, -1):
return False
# Check for connectivity
return len(visited) == n
Python
class Solution {
public boolean validTree(int n, int[][] edges) {
// Pre-check for edge count
if (edges.length != n - 1) {
return false;
}
// Build adjacency list
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
// Set to track visited nodes
Set<Integer> visited = new HashSet<>();
// DFS function
if (!dfs(0, -1, graph, visited)) {
return false;
}
// Check connectivity
return visited.size() == n;
}
private boolean dfs(int node, int parent, Map<Integer, List<Integer>> graph, Set<Integer> visited) {
if (visited.contains(node)) {
return false;
}
visited.add(node);
for (int nbr : graph.get(node)) {
if (nbr != parent) {
if (!dfs(nbr, node, graph, visited)) {
return false;
}
}
}
return true;
}
}
Complexity
- ⏰ Time complexity:
O(n + e)
wheren
is the number of nodes ande
is the number of edges. This accounts for iterating through nodes and edges. - 🧺 Space complexity:
O(n + e)
for the graph representation and the visited set.
Method 2 - BFS
The BFS approach ensures:
- No Cycles: During the traversal, if we encounter a node that has already been visited (and is not the parent of the current node), then we have a cycle.
- Connectivity: After traversal, all nodes must have been visited to confirm connectivity.
Code
Java
class Solution {
public boolean validTree(int n, int[][] edges) {
if (edges.length != n - 1) {
return false;
}
// Build adjacency list
Map<Integer, List<Integer>> graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
// BFS traversal
Set<Integer> visited = new HashSet<>();
Queue<int[]> queue = new LinkedList<>();
queue.add(new int[] {0, -1}); // {node, parent}
while (!queue.isEmpty()) {
int[] current = queue.poll();
int node = current[0];
int parent = current[1];
if (visited.contains(node)) {
return false;
}
visited.add(node);
for (int nbr : graph.get(node)) {
if (nbr != parent) {
queue.add(new int[] {nbr, node});
}
}
}
// Ensure all nodes are connected
return visited.size() == n;
}
}
Python
class Solution:
def validTree(self, n: int, edges: List[List[int]]) -> bool:
if len(edges) != n - 1:
return False
# Construct adjacency list
graph = {i: [] for i in range(n)}
for u, v in edges:
graph[u].append(v)
graph[v].append(u)
visited = set()
queue: Deque[(int, int)] = deque([(0, -1)]) # (node, parent)
while queue:
node, parent = queue.popleft()
if node in visited:
return False
visited.add(node)
for nbr in graph[node]:
if nbr != parent: # Avoid revisiting the parent
queue.append((nbr, node))
# Check for connectivity
return len(visited) == n
Complexity
- ⏰ Time complexity:
O(n + e)
- Adding edges to the graph takes
O(e)
. - BFS traversal takes
O(n + e)
for nodes and edges.
- Adding edges to the graph takes
- 🧺 Space complexity:
O(n + e)
- The adjacency list takes
O(n + e)
. - The queue and visited set together take
O(n)
.
- The adjacency list takes