298. Binary Tree Longest Consecutive Sequence 🔒
Problem Description
You are given the root of a binary tree. Your task is to find the length of the longest consecutive sequence path in the tree.
A consecutive sequence path is defined as a path where each node's value is exactly one more than its parent node's value in the path. For example, a path with values 1 → 2 → 3
is a consecutive sequence.
Key constraints to note:
- The path can start at any node in the tree (not necessarily the root)
- The path must follow parent-to-child connections only (you cannot traverse from a child back to its parent)
- The values must increase by exactly 1 at each step along the path
For example, if you have a tree where a parent node has value 5
, only a child with value 6
would continue the consecutive sequence. A child with value 7
or 4
would start a new sequence.
The function should return an integer representing the maximum length of any consecutive sequence path found in the tree.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: The problem involves a binary tree, which is a special type of graph with nodes (tree nodes) and edges (parent-child relationships).
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure where each node has at most two children and there are no cycles.
DFS
- Conclusion: Since we're dealing with a tree structure, the flowchart leads us directly to using Depth-First Search (DFS).
Why DFS is the Right Choice
The flowchart correctly identifies DFS as the optimal approach for this problem because:
-
Tree Traversal: We need to explore all possible paths in the tree to find the longest consecutive sequence, which naturally fits a tree traversal pattern.
-
Path-based Problem: The problem requires tracking consecutive sequences along parent-to-child paths, which DFS handles naturally by exploring one complete path before backtracking.
-
Bottom-up Information: We need to compute the longest consecutive sequence starting from each node, which requires information from children nodes first - a perfect fit for post-order DFS traversal.
-
State Tracking: DFS allows us to maintain the current consecutive sequence length as we traverse down the tree and update the global maximum as we backtrack.
The solution uses DFS to recursively explore each subtree, calculate the longest consecutive path starting from each node, and maintain a global maximum throughout the traversal.
Intuition
When thinking about finding the longest consecutive sequence in a binary tree, we need to consider that a valid sequence must follow parent-to-child connections where each child's value is exactly one more than its parent.
The key insight is that at any node, we have two potential paths to extend: through the left child or through the right child. We need to know the maximum consecutive sequence length that can be formed starting from each child.
This naturally suggests a bottom-up approach: first figure out the answer for the children, then use that information to compute the answer for the parent. This is exactly what post-order DFS gives us.
For each node, we ask: "What's the longest consecutive sequence I can form starting from this node and going down?" To answer this:
- First, recursively compute the longest sequences starting from the left and right children
- Check if each child continues our sequence (child.val = current.val + 1)
- If a child doesn't continue the sequence, the path through that child has length 1 (just the current node)
- Take the maximum of the two paths (left and right)
The clever part is maintaining a global maximum as we compute these local values. Since a consecutive sequence can start at any node in the tree, not just the root, we need to track the best sequence we've seen anywhere in the tree.
By using nonlocal ans
to maintain this global maximum and updating it at each node with ans = max(ans, t)
, we ensure we capture the longest consecutive sequence regardless of where it starts in the tree. The DFS returns the longest path starting from each node (needed for parent calculations), while simultaneously updating the global answer.
Solution Approach
The solution implements a recursive DFS approach with post-order traversal to find the longest consecutive sequence in the binary tree.
Core Algorithm Structure:
The implementation uses a helper function dfs(root)
that returns the length of the longest consecutive sequence starting from the current node and going downward.
Step-by-step Implementation:
-
Base Case: If the current node is
None
, return 0 since there's no sequence to form. -
Recursive Calls: First, recursively compute the longest consecutive sequences from both children:
l = dfs(root.left) + 1 r = dfs(root.right) + 1
We add 1 to include the current node in the potential sequence length.
-
Validate Consecutive Property: Check if each child actually continues the consecutive sequence:
if root.left and root.left.val - root.val != 1: l = 1 if root.right and root.right.val - root.val != 1: r = 1
If a child exists but its value isn't exactly one more than the current node's value, reset that path's length to 1 (only the current node).
-
Calculate Local Maximum: Find the longest consecutive sequence starting from the current node:
t = max(l, r)
-
Update Global Maximum: Use the
nonlocal
keyword to update the global answer:nonlocal ans ans = max(ans, t)
This ensures we track the longest sequence found anywhere in the tree.
-
Return Value: Return
t
to the parent call, which represents the longest consecutive sequence starting from this node.
Main Function:
- Initialize
ans = 0
to track the global maximum - Call
dfs(root)
to traverse the entire tree - Return the final answer
The time complexity is O(n)
where n
is the number of nodes, as we visit each node exactly once. The space complexity is O(h)
where h
is the height of the tree, due to the recursion call stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let me walk through a small example to illustrate how this solution works.
Consider this binary tree:
1 / \ 3 2 / \ 2 3
Step-by-step DFS traversal (post-order):
-
Visit leftmost leaf (value = 2):
dfs(node_2)
at bottom left- Both children are
None
, sol = 0 + 1 = 1
,r = 0 + 1 = 1
- No children exist, so consecutive checks are skipped
t = max(1, 1) = 1
- Update global:
ans = max(0, 1) = 1
- Return 1 to parent
-
Visit node with value = 3 (left child of root):
dfs(node_3)
- Left child returned 1, so
l = 1 + 1 = 2
- Right child is
None
, sor = 0 + 1 = 1
- Check left child:
2 - 3 = -1 ≠1
, so resetl = 1
t = max(1, 1) = 1
- Global remains:
ans = max(1, 1) = 1
- Return 1 to parent
-
Visit rightmost leaf (value = 3):
dfs(node_3)
at bottom right- Both children are
None
, sol = 1
,r = 1
t = 1
- Global remains:
ans = max(1, 1) = 1
- Return 1 to parent
-
Visit node with value = 2 (right child of root):
dfs(node_2)
- Left child is
None
, sol = 1
- Right child returned 1, so
r = 1 + 1 = 2
- Check right child:
3 - 2 = 1 ✓
, sor
stays 2 t = max(1, 2) = 2
- Update global:
ans = max(1, 2) = 2
- Return 2 to parent
-
Visit root (value = 1):
dfs(node_1)
- Left child returned 1, so
l = 1 + 1 = 2
- Right child returned 2, so
r = 2 + 1 = 3
- Check left child:
3 - 1 = 2 ≠1
, so resetl = 1
- Check right child:
2 - 1 = 1 ✓
, sor
stays 3 t = max(1, 3) = 3
- Update global:
ans = max(2, 3) = 3
- Return 3
Final answer: 3
The longest consecutive sequence is 1 → 2 → 3
(root to right child to right grandchild).
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 Optional
9
10class Solution:
11 def longestConsecutive(self, root: Optional[TreeNode]) -> int:
12 """
13 Find the length of the longest consecutive sequence path in a binary tree.
14 The path refers to any sequence of nodes from some starting node to any node
15 in the tree along the parent-child connections where consecutive nodes differ by 1.
16 """
17
18 def dfs(node: Optional[TreeNode]) -> int:
19 """
20 Depth-first search to find the longest consecutive sequence starting from current node.
21
22 Args:
23 node: Current tree node being processed
24
25 Returns:
26 Length of longest consecutive sequence starting from current node going downward
27 """
28 # Base case: empty node returns 0
29 if node is None:
30 return 0
31
32 # Recursively get the longest consecutive sequence from left and right children
33 # Add 1 to include the current node in the sequence
34 left_length = dfs(node.left) + 1
35 right_length = dfs(node.right) + 1
36
37 # Check if left child forms a consecutive sequence with current node
38 # If not consecutive (child.val != parent.val + 1), reset length to 1
39 if node.left and node.left.val - node.val != 1:
40 left_length = 1
41
42 # Check if right child forms a consecutive sequence with current node
43 # If not consecutive (child.val != parent.val + 1), reset length to 1
44 if node.right and node.right.val - node.val != 1:
45 right_length = 1
46
47 # Get the maximum consecutive sequence length from current node
48 current_max = max(left_length, right_length)
49
50 # Update global maximum if current path is longer
51 nonlocal max_length
52 max_length = max(max_length, current_max)
53
54 # Return the longest consecutive sequence starting from current node
55 return current_max
56
57 # Initialize global maximum length
58 max_length = 0
59
60 # Start DFS traversal from root
61 dfs(root)
62
63 return max_length
64
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 // Global variable to track the maximum consecutive sequence length
18 private int maxLength;
19
20 /**
21 * Finds the length of the longest consecutive sequence path in a binary tree.
22 * The path can start from any node and go downwards.
23 *
24 * @param root The root node of the binary tree
25 * @return The length of the longest consecutive sequence
26 */
27 public int longestConsecutive(TreeNode root) {
28 maxLength = 0;
29 findLongestPath(root);
30 return maxLength;
31 }
32
33 /**
34 * DFS helper method to find the longest consecutive path starting from current node.
35 * Returns the length of the longest consecutive path that includes the current node
36 * and extends downward.
37 *
38 * @param node The current node being processed
39 * @return The length of the longest consecutive path starting from this node
40 */
41 private int findLongestPath(TreeNode node) {
42 // Base case: null node contributes 0 to the path length
43 if (node == null) {
44 return 0;
45 }
46
47 // Recursively calculate the longest consecutive path from left and right children
48 // Add 1 to include the current node in the path
49 int leftPath = findLongestPath(node.left) + 1;
50 int rightPath = findLongestPath(node.right) + 1;
51
52 // Check if left child continues the consecutive sequence
53 // If not consecutive (child value != parent value + 1), reset the path length to 1
54 if (node.left != null && node.left.val - node.val != 1) {
55 leftPath = 1;
56 }
57
58 // Check if right child continues the consecutive sequence
59 // If not consecutive (child value != parent value + 1), reset the path length to 1
60 if (node.right != null && node.right.val - node.val != 1) {
61 rightPath = 1;
62 }
63
64 // The longest path through current node is the maximum of left and right paths
65 int currentMaxPath = Math.max(leftPath, rightPath);
66
67 // Update the global maximum if current path is longer
68 maxLength = Math.max(maxLength, currentMaxPath);
69
70 // Return the longest consecutive path starting from current node
71 return currentMaxPath;
72 }
73}
74
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 int longestConsecutive(TreeNode* root) {
15 int maxLength = 0;
16
17 // DFS function to find the longest consecutive sequence starting from each node
18 // Returns the length of the longest consecutive path starting from the current node
19 function<int(TreeNode*)> dfs = [&](TreeNode* node) -> int {
20 // Base case: empty node contributes 0 to the path length
21 if (!node) {
22 return 0;
23 }
24
25 // Recursively get the longest consecutive paths from left and right children
26 // Add 1 to include the current node in the path
27 int leftLength = dfs(node->left) + 1;
28 int rightLength = dfs(node->right) + 1;
29
30 // Check if left child forms a consecutive sequence with current node
31 // If not consecutive (child value != parent value + 1), reset the path length to 1
32 if (node->left && node->left->val - node->val != 1) {
33 leftLength = 1;
34 }
35
36 // Check if right child forms a consecutive sequence with current node
37 // If not consecutive (child value != parent value + 1), reset the path length to 1
38 if (node->right && node->right->val - node->val != 1) {
39 rightLength = 1;
40 }
41
42 // Get the maximum consecutive path length passing through current node
43 int currentMaxLength = max(leftLength, rightLength);
44
45 // Update the global maximum length if current path is longer
46 maxLength = max(maxLength, currentMaxLength);
47
48 // Return the longest consecutive path starting from current node
49 return currentMaxLength;
50 };
51
52 // Start DFS traversal from root
53 dfs(root);
54
55 return maxLength;
56 }
57};
58
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Finds the length of the longest consecutive sequence path in a binary tree.
17 * A consecutive sequence path is a path where each node's value is exactly 1 more than its parent.
18 * @param root - The root node of the binary tree
19 * @returns The length of the longest consecutive sequence path
20 */
21function longestConsecutive(root: TreeNode | null): number {
22 // Global variable to track the maximum consecutive sequence length found
23 let maxConsecutiveLength: number = 0;
24
25 /**
26 * Depth-first search helper function to find consecutive sequences.
27 * Returns the length of the longest consecutive sequence starting from the current node.
28 * @param node - The current node being processed
29 * @returns The length of consecutive sequence from this node downward
30 */
31 const findConsecutiveLength = (node: TreeNode | null): number => {
32 // Base case: if node is null, return 0
33 if (node === null) {
34 return 0;
35 }
36
37 // Recursively get the consecutive length from left and right subtrees
38 // Add 1 to include the current node in the sequence
39 let leftLength: number = findConsecutiveLength(node.left) + 1;
40 let rightLength: number = findConsecutiveLength(node.right) + 1;
41
42 // Check if left child breaks the consecutive sequence
43 // If left child doesn't exist or its value is not current value + 1, reset to 1
44 if (node.left && node.left.val - node.val !== 1) {
45 leftLength = 1;
46 }
47
48 // Check if right child breaks the consecutive sequence
49 // If right child doesn't exist or its value is not current value + 1, reset to 1
50 if (node.right && node.right.val - node.val !== 1) {
51 rightLength = 1;
52 }
53
54 // Get the maximum consecutive length from current node (either left or right path)
55 const currentMaxLength: number = Math.max(leftLength, rightLength);
56
57 // Update the global maximum if current path is longer
58 maxConsecutiveLength = Math.max(maxConsecutiveLength, currentMaxLength);
59
60 // Return the maximum consecutive length starting from this node
61 return currentMaxLength;
62 };
63
64 // Start the DFS traversal from root
65 findConsecutiveLength(root);
66
67 // Return the maximum consecutive sequence length found
68 return maxConsecutiveLength;
69}
70
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree.
The algorithm performs a depth-first search (DFS) traversal of the binary tree. Each node is visited exactly once during the traversal. At each node, the algorithm performs constant time operations:
- Checking if the node is null
- Recursive calls to left and right children
- Comparing values with children (if they exist)
- Updating local and global maximum values
Since we visit each of the n
nodes exactly once and perform O(1)
work at each node, the overall time complexity is O(n)
.
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 skewed tree (essentially a linked list), the height
h = n
, resulting inO(n)
space complexity - For a balanced tree, the height
h = log(n)
, resulting inO(log n)
space complexity
The algorithm uses a constant amount of extra space for variables (l
, r
, t
, and the global ans
), but this doesn't affect the overall space complexity which is dominated by the recursion stack depth.
Therefore, the space complexity is O(h)
, which ranges from O(log n)
in the best case (balanced tree) to O(n)
in the worst case (skewed tree).
Common Pitfalls
Pitfall 1: Incorrectly Handling the Sequence Direction
The Problem:
A common mistake is misunderstanding how the consecutive sequence should flow. Some developers might incorrectly check if the parent's value is one more than the child's value (root.val - child.val == 1
) instead of checking if the child's value is one more than the parent's value.
Incorrect Implementation:
# WRONG: Checking parent > child by 1 if root.left and root.val - root.left.val != 1: left_length = 1
Correct Implementation:
# CORRECT: Checking child > parent by 1 if root.left and root.left.val - root.val != 1: left_length = 1
Pitfall 2: Forgetting to Add 1 After Recursive Calls
The Problem: When calculating the consecutive sequence length, developers might forget to add 1 to include the current node in the path length, leading to off-by-one errors.
Incorrect Implementation:
# WRONG: Forgetting to include current node left_length = dfs(node.left) right_length = dfs(node.right)
Correct Implementation:
# CORRECT: Adding 1 to include current node left_length = dfs(node.left) + 1 right_length = dfs(node.right) + 1
Pitfall 3: Not Checking for Child Existence Before Accessing Values
The Problem:
Attempting to access the val
attribute of a child node without first checking if the child exists will cause an AttributeError when the child is None
.
Incorrect Implementation:
# WRONG: May cause AttributeError if node.left is None if node.left.val - node.val != 1: left_length = 1
Correct Implementation:
# CORRECT: Check existence first if node.left and node.left.val - node.val != 1: left_length = 1
Pitfall 4: Returning Wrong Value from DFS Function
The Problem: Some might mistakenly return the global maximum from the DFS function instead of the local maximum starting from the current node. This breaks the recursive logic as parent nodes need to know the longest path starting from their children, not the global maximum.
Incorrect Implementation:
def dfs(node):
# ... calculation logic ...
nonlocal max_length
max_length = max(max_length, current_max)
return max_length # WRONG: Returns global max
Correct Implementation:
def dfs(node):
# ... calculation logic ...
nonlocal max_length
max_length = max(max_length, current_max)
return current_max # CORRECT: Returns local max from current node
Pitfall 5: Initializing Answer Variable Incorrectly
The Problem: Initializing the answer to 1 instead of 0 can cause issues when the tree is empty or when the actual maximum length is 0.
Incorrect Implementation:
max_length = 1 # WRONG: Assumes at least one node exists
Correct Implementation:
max_length = 0 # CORRECT: Handles empty tree case
Prevention Tips:
- Always trace through the algorithm with a simple example to verify the logic
- Consider edge cases like empty trees, single nodes, and paths that don't start from the root
- Draw out the parent-child relationships and verify the direction of value comparison
- Test with trees where no consecutive sequences exist (should return 1 if tree has nodes)
Which of these pictures shows the visit order of a depth-first search?
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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!