261. Graph Valid Tree - Explanation

Problem Link

Description

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.

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

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.

Constraints:

  • 1 <= n <= 100
  • 0 <= edges.length <= n * (n - 1) / 2


Topics

Recommended Time & Space Complexity

You should aim for a solution as good or better than O(V + E) time and O(V + E) space, where V is the number vertices and E is the number of edges in the graph.


Hint 1

According to the definition of a tree, a tree is an undirected graph with no cycles, all the nodes are connected as one component, and any two nodes have exactly one path. Can you think of a recursive algorithm to detect a cycle in the given graph and ensure it is a tree?


Hint 2

We can use the Depth First Search (DFS) algorithm to detect a cycle in the graph. Since a tree has only one component, we can start the DFS from any node, say node 0. During the traversal, we recursively visit its neighbors (children). If we encounter any already visited node that is not the parent of the current node, we return false as it indicates a cycle. How would you implement this?


Hint 3

We start DFS from node 0, assuming -1 as its parent. We initialize a hash set visit to track the visited nodes in the graph. During the DFS, we first check if the current node is already in visit. If it is, we return false, detecting a cycle. Otherwise, we mark the node as visited and perform DFS on its neighbors, skipping the parent node to avoid revisiting it. After all DFS calls, if we have visited all nodes, we return true, as the graph is connected. Otherwise, we return false because a tree must contain all nodes.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Graph Representation - Building and working with adjacency lists for undirected graphs
  • Depth-First Search (DFS) - Traversing graphs recursively while tracking visited nodes
  • Breadth-First Search (BFS) - Level-by-level graph traversal using a queue
  • Union-Find (Disjoint Set Union) - Efficiently tracking connected components and detecting cycles

1. Cycle Detection (DFS)

Intuition

A graph is a valid tree if:

  1. It has no cycles
  2. It is fully connected

Using DFS, we can detect cycles by checking if we visit a node again from a path other than its parent.
Also, a tree with n nodes must have exactly n - 1 edges — otherwise it’s invalid.

Algorithm

  1. If number of edges > n - 1, return false.
  2. Build an adjacency list for the undirected graph.
  3. Run dfs from node 0, passing the parent to avoid false cycle detection.
  4. If dfs finds a visited node (not the parent), a cycle exists → return false.
  5. After dfs, check if all nodes were visited (graph is connected).
  6. Return true only if no cycle and all nodes are visited.
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if len(edges) > (n - 1):
            return False

        adj = [[] for _ in range(n)]
        for u, v in edges:
            adj[u].append(v)
            adj[v].append(u)

        visit = set()
        def dfs(node, par):
            if node in visit:
                return False

            visit.add(node)
            for nei in adj[node]:
                if nei == par:
                    continue
                if not dfs(nei, node):
                    return False
            return True

        return dfs(0, -1) and len(visit) == n

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V+E)O(V + E)

Where VV is the number vertices and EE is the number of edges in the graph.


Intuition

A graph is a valid tree if:

  1. It has no cycles
  2. It is fully connected

Using BFS, we traverse the graph level by level.

  • If we ever reach a node that was already visited (and is not the parent) → a cycle exists.
  • After BFS, if all nodes are visited, the graph is connected.

Also, a tree with n nodes can have at most n - 1 edges.

Algorithm

  1. If number of edges > n - 1, return false.
  2. Build an adjacency list for the undirected graph.
  3. Start bfs from node 0, store (node, parent) in the queue.
  4. For each neighbor:
    • Ignore the parent.
    • If already visited → cycle found → return false.
    • Otherwise, mark visited and add to queue.
  5. After bfs, check if visited node count equals n.
  6. Return true only if no cycle and all nodes are visited.
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if len(edges) > n - 1:
            return False

        adj = [[] for _ in range(n)]
        for u, v in edges:
            adj[u].append(v)
            adj[v].append(u)

        visit = set()
        q = deque([(0, -1)])  # (current node, parent node)
        visit.add(0)

        while q:
            node, parent = q.popleft()
            for nei in adj[node]:
                if nei == parent:
                    continue
                if nei in visit:
                    return False
                visit.add(nei)
                q.append((nei, node))

        return len(visit) == n

Time & Space Complexity

  • Time complexity: O(V+E)O(V + E)
  • Space complexity: O(V+E)O(V + E)

Where VV is the number vertices and EE is the number of edges in the graph.


3. Disjoint Set Union

Intuition

A graph is a valid tree if:

  1. It has no cycles
  2. It is fully connected

Using Disjoint Set Union (Union-Find):

  • Each node starts in its own component
  • When we connect two nodes:
    • If they are already in the same component, adding this edge creates a cycle
    • Otherwise, we merge their components
  • In the end, a valid tree must have exactly one connected component

Also, a tree with n nodes can have at most n - 1 edges.

Algorithm

  1. If number of edges > n - 1, return false.
  2. Initialize DSU with n components.
  3. For each edge (u, v):
    • If union(u, v) fails → cycle detected → return false.
  4. After processing all edges:
    • Check if number of components is 1.
  5. Return true if only one component exists, else false.
class DSU:
    def __init__(self, n):
        self.comps = n
        self.Parent = list(range(n + 1))
        self.Size = [1] * (n + 1)

    def find(self, node):
        if self.Parent[node] != node:
            self.Parent[node] = self.find(self.Parent[node])
        return self.Parent[node]

    def union(self, u, v):
        pu = self.find(u)
        pv = self.find(v)
        if pu == pv:
            return False

        self.comps -= 1
        if self.Size[pu] < self.Size[pv]:
            pu, pv = pv, pu
        self.Size[pu] += self.Size[pv]
        self.Parent[pv] = pu
        return True

    def components(self):
        return self.comps

class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if len(edges) > n - 1:
            return False

        dsu = DSU(n)
        for u, v in edges:
            if not dsu.union(u, v):
                return False
        return dsu.components() == 1

Time & Space Complexity

  • Time complexity: O(V+(Eα(V)))O(V + (E * α(V)))
  • Space complexity: O(V)O(V)

Where VV is the number of vertices and EE is the number of edges in the graph. α()α() is used for amortized complexity.


Common Pitfalls

Detecting False Cycles Due to Parent Edges

In an undirected graph represented with an adjacency list, each edge (u, v) appears twice: once in adj[u] and once in adj[v]. When traversing from node u to v, you will see u in v's neighbor list. Without tracking the parent, this would incorrectly be detected as a cycle. Always pass and check the parent node to avoid this false positive.

Forgetting to Check Connectivity

A graph can be cycle-free but still not be a valid tree if it is disconnected. After running DFS or BFS from any starting node, you must verify that all n nodes were visited. If visited.size() < n, the graph has multiple connected components and is not a valid tree.

Not Using the Edge Count Shortcut

A valid tree with n nodes must have exactly n - 1 edges. If there are more than n - 1 edges, the graph definitely has a cycle. If there are fewer than n - 1 edges, the graph cannot be connected. Checking this condition first can save unnecessary traversal and provides an early exit for invalid inputs.