Graph Valid Tree medium
Problem Statement
Given n
nodes labeled from 0
to n - 1
and a list of edges
, where edges[i] = [ui, vi]
represents a directed edge from node ui
to node vi
, determine if the edges form a valid, undirected tree. A valid tree is a connected, acyclic graph. In other words, it is a connected graph with n
nodes and n - 1
edges.
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],[3,4],[4,0]] Output: false
Explanation: This forms a cycle, so it's not a valid tree.
Steps
-
Validate Number of Edges: A valid tree with
n
nodes hasn - 1
edges. If the number of edges doesn't match this, it's not a tree. -
Detect Cycles (Using Depth-First Search (DFS) or Breadth-First Search (BFS)): We'll use DFS to traverse the graph. We need to keep track of visited nodes and nodes currently in the recursion stack (to detect cycles). If we encounter a node that's already been visited and is in the recursion stack, it indicates a cycle.
-
Check Connectivity: After DFS, we check if all nodes have been visited. If not, the graph is not connected.
Explanation
The solution uses Depth-First Search (DFS) to check for cycles and connectivity simultaneously. The dfs
function recursively explores the graph.
visited
: A boolean array to keep track of visited nodes.recursionStack
: A boolean array to keep track of nodes currently in the recursion stack (during the DFS call). This is crucial for cycle detection.graph
: An adjacency list representation of the graph for efficient neighbor lookup.
The isValidTree
function first checks the number of edges. Then, it performs DFS starting from node 0. If a cycle is detected or not all nodes are visited after DFS, it returns false
. Otherwise, it returns true
.
Code
import java.util.*;
class Solution {
public boolean validTree(int n, int[][] edges) {
if (edges.length != n - 1) return false; // Check edge count
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]); // Undirected graph
}
boolean[] visited = new boolean[n];
boolean[] recursionStack = new boolean[n];
if (hasCycle(graph, 0, visited, recursionStack)) return false;
for (boolean b : visited) {
if (!b) return false; // Check connectivity
}
return true;
}
private boolean hasCycle(List<List<Integer>> graph, int node, boolean[] visited, boolean[] recursionStack) {
visited[node] = true;
recursionStack[node] = true;
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
if (hasCycle(graph, neighbor, visited, recursionStack)) return true;
} else if (recursionStack[neighbor]) {
return true; // Cycle detected
}
}
recursionStack[node] = false; // Remove from recursion stack after exploring
return false;
}
}
Complexity
- Time Complexity: O(V + E), where V is the number of vertices (nodes) and E is the number of edges. This is because DFS visits each node and edge once.
- Space Complexity: O(V), due to the
visited
andrecursionStack
arrays, and the adjacency list representation of the graph. The recursion stack in the DFS call also contributes to space complexity.