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:
- Traverse each potential root-to-leaf path
- Compare node values with array elements at each step
- Verify we've reached a leaf when we've consumed all array elements
- 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.
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:
- We must start checking from the root (array index 0)
- We must end exactly at a leaf node when we've checked all array elements
- 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 examinedu
: 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:
- If we've reached a
None
node (went beyond a leaf), the path is invalid - 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)
wheren
is the number of nodes. In the worst case, we might visit every node once - Space:
O(h)
whereh
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 EvaluatorExample 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:
-
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)
- Check:
-
Explore left child (value=2), u=1:
- Check:
root.val (2) == arr[1] (3)
✗ No match! - Return
False
immediately (early termination)
- Check:
-
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)
- Check:
-
Explore node 3's left child (value=1), u=2:
- Check:
root.val (1) == arr[2] (5)
✗ No match! - Return
False
- Check:
-
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
- Check:
-
Result propagates back:
- Node 3 returns
False or True = True
- Root returns
False or True = True
- Final answer: True
- Node 3 returns
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 returnTrue
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 isO(log n)
- For a skewed tree:
h = O(n)
, so space complexity isO(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)
Which of the following problems can be solved with backtracking (select multiple)
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!