Facebook Pixel

1430. Check If a String Is a Valid Sequence from Root to Leaves Path in a Binary Tree 🔒

Problem Description

You are given a binary tree and an array of integers arr. You need to determine if the array represents a valid root-to-leaf path in the tree.

A valid sequence means:

  • The path must start from the root node
  • The path must end at a leaf node (a node with no children)
  • The values of nodes along the path, when read from root to leaf, must exactly match the values in the given array arr in the same order

For example, if arr = [1, 2, 3], then there must exist a path from root to leaf where:

  • The root node has value 1
  • Its child (along the path) has value 2
  • That child's child (which must be a leaf) has value 3

The solution uses a depth-first search (DFS) approach:

  • Starting from the root, it traverses the tree while tracking the current position u in the array
  • At each node, it checks if the node's value matches arr[u]
  • If we've reached the last element of the array (u == len(arr) - 1), we verify that the current node is a leaf
  • Otherwise, we recursively check both left and right children with the next array position (u + 1)
  • The function returns True if any path from root to leaf matches the array sequence

The time complexity is O(n) where n is the number of nodes in the tree, as we might visit each node once in the worst case. The space complexity is O(h) where h is the height of the tree, due to the recursion stack.

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.

Is it a tree?

  • Yes: The problem explicitly states we're working with a binary tree structure.

DFS

  • Since we answered "yes" to the tree question, the flowchart leads us directly to DFS (Depth-First Search).

Why DFS is appropriate for this problem:

  • We need to explore paths from root to leaf nodes
  • We need to validate each path against the given array sequence
  • DFS naturally follows paths from root to leaf, making it ideal for checking if a complete path matches our array
  • At each node, we can check if the current value matches the corresponding array element
  • We can backtrack if a path doesn't match and try alternative paths

Conclusion: The flowchart correctly suggests using DFS for this tree traversal problem. The DFS approach allows us to:

  1. Traverse each potential root-to-leaf path
  2. Compare node values with array elements at each step
  3. Verify we've reached a leaf when we've consumed all array elements
  4. Backtrack and explore other paths if the current path doesn't match

This aligns perfectly with the solution's recursive DFS implementation that checks each path while maintaining the current position in the array.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we think about validating a sequence against a path in a tree, we need to match two things simultaneously: the structure of the path and the values along that path.

The key insight is that we're looking for a complete root-to-leaf path that matches our array. This means:

  1. We must start checking from the root (array index 0)
  2. We must end exactly at a leaf node when we've checked all array elements
  3. Every node along the path must match the corresponding array value

Think of it like following directions where each number tells you what the next landmark should be. If you're at position u in the array, the current node must have value arr[u]. If it doesn't match, this path is wrong and we can stop exploring it immediately.

The natural way to explore paths in a tree is through DFS - we go as deep as possible along one path before backtracking. As we traverse:

  • We carry along our position u in the array
  • At each node, we first check if node.val == arr[u] (if not, this path fails)
  • If we've matched all array elements (u == len(arr) - 1), we check if we're at a leaf
  • Otherwise, we try both children with the next array position (u + 1)

The beauty of this approach is that we can short-circuit as soon as we find a mismatch, and the recursive nature of DFS handles the backtracking automatically. We only need one valid path to exist, so we use or between the left and right recursive calls - if either subtree contains a valid path, we return True.

This is more efficient than generating all root-to-leaf paths first and then checking them, as we validate incrementally and abandon invalid paths early.

Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.

Solution Approach

The solution implements a recursive DFS approach with early termination conditions. Let's walk through the implementation step by step:

Main Function Setup:

def isValidSequence(self, root: TreeNode, arr: List[int]) -> bool:
    return dfs(root, 0)

We start the DFS from the root node with array index 0.

DFS Helper Function:

def dfs(root, u):

The helper function takes two parameters:

  • root: current node being examined
  • u: current index in the array we're trying to match

Base Cases and Early Termination:

if root is None or root.val != arr[u]:
    return False

This handles two failure conditions:

  1. If we've reached a None node (went beyond a leaf), the path is invalid
  2. If the current node's value doesn't match arr[u], this path cannot be valid

Leaf Node Validation:

if u == len(arr) - 1:
    return root.left is None and root.right is None

When we've matched all elements in the array (u is at the last index), we must verify we're at a leaf node. A leaf has no left or right children. This ensures:

  • We don't have a path that's too short (array has more elements than the path)
  • We don't have a path that's too long (path continues beyond the array length)

Recursive Exploration:

return dfs(root.left, u + 1) or dfs(root.right, u + 1)

If we haven't reached the end of the array yet, we explore both children:

  • Try the left subtree with the next array element (u + 1)
  • Try the right subtree with the next array element (u + 1)
  • Use or because we only need ONE valid path to exist

Time and Space Complexity:

  • Time: O(n) where n is the number of nodes. In the worst case, we might visit every node once
  • Space: O(h) where h is the height of the tree, representing the maximum recursion depth

The algorithm efficiently prunes invalid paths early and returns True as soon as any valid sequence is found.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example to illustrate how the DFS solution works.

Example Tree:

       1
      / \
     2   3
    /   / \
   4   1   5

Array to validate: arr = [1, 3, 5]

Step-by-step DFS traversal:

  1. Start at root (value=1), u=0:

    • Check: root.val (1) == arr[0] (1) ✓ Match!
    • Not at end of array (u=0, not 2), so explore children
    • Call dfs(left_child, 1) or dfs(right_child, 1)
  2. Explore left child (value=2), u=1:

    • Check: root.val (2) == arr[1] (3) ✗ No match!
    • Return False immediately (early termination)
  3. Backtrack and explore right child (value=3), u=1:

    • Check: root.val (3) == arr[1] (3) ✓ Match!
    • Not at end of array (u=1, not 2), so explore children
    • Call dfs(left_child, 2) or dfs(right_child, 2)
  4. Explore node 3's left child (value=1), u=2:

    • Check: root.val (1) == arr[2] (5) ✗ No match!
    • Return False
  5. Backtrack and explore node 3's right child (value=5), u=2:

    • Check: root.val (5) == arr[2] (5) ✓ Match!
    • We're at end of array (u=2, which is len(arr)-1)
    • Check if leaf: left is None and right is None ✓ It's a leaf!
    • Return True
  6. Result propagates back:

    • Node 3 returns False or True = True
    • Root returns False or True = True
    • Final answer: True

The path 1 → 3 → 5 is a valid root-to-leaf sequence matching our array.

Key observations from this walkthrough:

  • We abandoned the left branch immediately when node 2 didn't match arr[1]
  • We only found the valid path when exploring the right subtree
  • The algorithm correctly verified that node 5 was a leaf when we finished matching the array
  • The or operation ensured that finding one valid path was sufficient to return True

Solution Implementation

1# Definition for a binary tree node.
2# class TreeNode:
3#     def __init__(self, val=0, left=None, right=None):
4#         self.val = val
5#         self.left = left
6#         self.right = right
7
8from typing import List, Optional
9
10class Solution:
11    def isValidSequence(self, root: Optional[TreeNode], arr: List[int]) -> bool:
12        """
13        Check if a given sequence from root to leaf exists in the binary tree.
14      
15        Args:
16            root: The root node of the binary tree
17            arr: The sequence to validate from root to leaf
18          
19        Returns:
20            True if the sequence exists as a root-to-leaf path, False otherwise
21        """
22      
23        def dfs(node: Optional[TreeNode], index: int) -> bool:
24            """
25            Depth-first search to validate the sequence.
26          
27            Args:
28                node: Current node in the tree
29                index: Current index in the array being checked
30              
31            Returns:
32                True if valid path exists from current node, False otherwise
33            """
34            # Base case: node is None or value doesn't match the sequence
35            if node is None or node.val != arr[index]:
36                return False
37          
38            # Check if we've reached the last element in the sequence
39            if index == len(arr) - 1:
40                # Valid only if current node is a leaf node
41                return node.left is None and node.right is None
42          
43            # Recursively check left and right subtrees for the next element
44            # Return True if either path leads to a valid sequence
45            return dfs(node.left, index + 1) or dfs(node.right, index + 1)
46      
47        # Start DFS from root with index 0
48        return dfs(root, 0)
49
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 *     int val;
5 *     TreeNode left;
6 *     TreeNode right;
7 *     TreeNode() {}
8 *     TreeNode(int val) { this.val = val; }
9 *     TreeNode(int val, TreeNode left, TreeNode right) {
10 *         this.val = val;
11 *         this.left = left;
12 *         this.right = right;
13 *     }
14 * }
15 */
16class Solution {
17    // Store the target sequence array as instance variable
18    private int[] targetSequence;
19
20    /**
21     * Validates if there exists a root-to-leaf path in the binary tree
22     * that matches the given sequence array
23     * 
24     * @param root The root node of the binary tree
25     * @param arr The target sequence to validate
26     * @return true if a valid root-to-leaf path exists matching the sequence, false otherwise
27     */
28    public boolean isValidSequence(TreeNode root, int[] arr) {
29        this.targetSequence = arr;
30        // Start DFS traversal from root with index 0
31        return dfs(root, 0);
32    }
33
34    /**
35     * Performs depth-first search to find a matching path
36     * 
37     * @param node The current tree node being processed
38     * @param index The current position in the target sequence array
39     * @return true if a valid path is found from this node, false otherwise
40     */
41    private boolean dfs(TreeNode node, int index) {
42        // Base case: if node is null or value doesn't match sequence at current index
43        if (node == null || node.val != targetSequence[index]) {
44            return false;
45        }
46      
47        // Check if we've reached the last element in the sequence
48        if (index == targetSequence.length - 1) {
49            // Valid only if current node is a leaf node
50            return node.left == null && node.right == null;
51        }
52      
53        // Recursively check left and right subtrees with next index
54        return dfs(node.left, index + 1) || dfs(node.right, index + 1);
55    }
56}
57
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 *     int val;
5 *     TreeNode *left;
6 *     TreeNode *right;
7 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14    /**
15     * Check if a given sequence from root to leaf exists in the binary tree
16     * @param root - The root node of the binary tree
17     * @param arr - The sequence array to validate
18     * @return true if the sequence exists as a root-to-leaf path, false otherwise
19     */
20    bool isValidSequence(TreeNode* root, vector<int>& arr) {
21        // Define a recursive DFS function to traverse the tree
22        // Parameters: current node and current index in the array
23        function<bool(TreeNode*, int)> dfs = [&](TreeNode* node, int index) -> bool {
24            // Base case: if node is null or value doesn't match current array element
25            if (!node || node->val != arr[index]) {
26                return false;
27            }
28          
29            // Check if we've reached the last element in the sequence
30            if (index == arr.size() - 1) {
31                // Valid sequence only if current node is a leaf node
32                return !node->left && !node->right;
33            }
34          
35            // Recursively check both left and right subtrees for the next element
36            return dfs(node->left, index + 1) || dfs(node->right, index + 1);
37        };
38      
39        // Start DFS from root with index 0
40        return dfs(root, 0);
41    }
42};
43
1/**
2 * Definition for a binary tree node.
3 */
4class TreeNode {
5    val: number;
6    left: TreeNode | null;
7    right: TreeNode | null;
8    constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
9        this.val = (val === undefined ? 0 : val);
10        this.left = (left === undefined ? null : left);
11        this.right = (right === undefined ? null : right);
12    }
13}
14
15/**
16 * Check if a given sequence from root to leaf exists in the binary tree
17 * @param root - The root node of the binary tree
18 * @param arr - The sequence array to validate
19 * @return true if the sequence exists as a root-to-leaf path, false otherwise
20 */
21function isValidSequence(root: TreeNode | null, arr: number[]): boolean {
22    /**
23     * Recursive DFS helper function to traverse the tree
24     * @param node - Current node being processed
25     * @param index - Current index in the sequence array
26     * @return true if valid sequence found from this node, false otherwise
27     */
28    const dfs = (node: TreeNode | null, index: number): boolean => {
29        // Base case: if node is null or node value doesn't match current array element
30        if (!node || node.val !== arr[index]) {
31            return false;
32        }
33      
34        // Check if we've reached the last element in the sequence
35        if (index === arr.length - 1) {
36            // Valid sequence only if current node is a leaf node (no children)
37            return !node.left && !node.right;
38        }
39      
40        // Recursively check both left and right subtrees for the next element in sequence
41        // Return true if either subtree contains the remaining sequence
42        return dfs(node.left, index + 1) || dfs(node.right, index + 1);
43    };
44  
45    // Start DFS traversal from root with index 0
46    return dfs(root, 0);
47}
48

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the binary tree.

In the worst case, we might need to traverse all nodes in the tree. This happens when:

  • Multiple paths have the same prefix as the array
  • We need to explore both left and right subtrees at various levels
  • The valid sequence (if it exists) is found near the rightmost part of our search

Although at each node we potentially explore both children (which might suggest O(2^h) where h is the height), the early termination conditions significantly prune the search:

  • We stop when root.val != arr[u] (value mismatch)
  • We stop when we've matched all array elements (u == len(arr) - 1)
  • We stop when we reach None nodes

Each node is visited at most once during the traversal, giving us O(n) time complexity.

Space Complexity: O(h) where h is the height of the binary tree.

The space complexity is determined by the recursive call stack. In the worst case:

  • For a balanced tree: h = O(log n), so space complexity is O(log n)
  • For a skewed tree: h = O(n), so space complexity is O(n)

The recursion depth is bounded by the minimum of:

  • The height of the tree h
  • The length of the array len(arr)

Since we're checking a root-to-leaf path and the array represents such a path, the recursion depth is O(min(h, len(arr))). In practice, this is O(h) as the array length cannot exceed the tree height for a valid sequence.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Checking for Leaf Node Requirement

A very common mistake is forgetting to verify that the path ends at a leaf node when the array is fully matched. Many implementations incorrectly return True as soon as all array elements are matched, without checking if the current node is actually a leaf.

Incorrect Implementation:

def dfs(node, index):
    if node is None or node.val != arr[index]:
        return False
  
    # WRONG: Returns True without checking if it's a leaf
    if index == len(arr) - 1:
        return True  # Bug: Path might continue beyond this node!
  
    return dfs(node.left, index + 1) or dfs(node.right, index + 1)

Why This Fails: Consider a tree where path [1, 2, 3] exists but node with value 3 has children. The array [1, 2, 3] would incorrectly return True even though the path doesn't end at a leaf.

Correct Implementation:

if index == len(arr) - 1:
    # Must verify this is a leaf node
    return node.left is None and node.right is None

2. Array Index Out of Bounds

Another pitfall occurs when not properly handling the array bounds, especially when the tree path is longer than the array.

Incorrect Implementation:

def dfs(node, index):
    # WRONG: Accessing arr[index] before checking bounds
    if node is None or node.val != arr[index]:
        return False
    # Rest of the code...

Why This Fails: If index >= len(arr), this will throw an IndexError. This can happen when traversing deeper paths in the tree.

Correct Implementation:

def dfs(node, index):
    # Check array bounds first
    if node is None or index >= len(arr) or node.val != arr[index]:
        return False
    # Rest of the code...

3. Handling Empty Array or None Root

The solution should gracefully handle edge cases like empty arrays or null root nodes.

Incomplete Implementation:

def isValidSequence(self, root, arr):
    # Doesn't handle edge cases
    return dfs(root, 0)

Robust Implementation:

def isValidSequence(self, root, arr):
    # Handle edge cases upfront
    if not arr or root is None:
        return False
    return dfs(root, 0)

4. Incorrect Recursion Logic with AND vs OR

Some implementations mistakenly use and instead of or when checking both subtrees, which would require BOTH paths to be valid instead of just one.

Incorrect Implementation:

# WRONG: Requires both left AND right paths to be valid
return dfs(node.left, index + 1) and dfs(node.right, index + 1)

Correct Implementation:

# Correct: Only ONE path needs to be valid
return dfs(node.left, index + 1) or dfs(node.right, index + 1)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following problems can be solved with backtracking (select multiple)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More