Facebook Pixel

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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

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

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:

  1. First, recursively compute the longest sequences starting from the left and right children
  2. Check if each child continues our sequence (child.val = current.val + 1)
  3. If a child doesn't continue the sequence, the path through that child has length 1 (just the current node)
  4. 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:

  1. Base Case: If the current node is None, return 0 since there's no sequence to form.

  2. 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.

  3. 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).

  4. Calculate Local Maximum: Find the longest consecutive sequence starting from the current node:

    t = max(l, r)
  5. 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.

  6. 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 Evaluator

Example 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):

  1. Visit leftmost leaf (value = 2):

    • dfs(node_2) at bottom left
    • Both children are None, so l = 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
  2. 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, so r = 0 + 1 = 1
    • Check left child: 2 - 3 = -1 ≠ 1, so reset l = 1
    • t = max(1, 1) = 1
    • Global remains: ans = max(1, 1) = 1
    • Return 1 to parent
  3. Visit rightmost leaf (value = 3):

    • dfs(node_3) at bottom right
    • Both children are None, so l = 1, r = 1
    • t = 1
    • Global remains: ans = max(1, 1) = 1
    • Return 1 to parent
  4. Visit node with value = 2 (right child of root):

    • dfs(node_2)
    • Left child is None, so l = 1
    • Right child returned 1, so r = 1 + 1 = 2
    • Check right child: 3 - 2 = 1 ✓, so r stays 2
    • t = max(1, 2) = 2
    • Update global: ans = max(1, 2) = 2
    • Return 2 to parent
  5. 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 reset l = 1
    • Check right child: 2 - 1 = 1 ✓, so r 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 in O(n) space complexity
  • For a balanced tree, the height h = log(n), resulting in O(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:

  1. Always trace through the algorithm with a simple example to verify the logic
  2. Consider edge cases like empty trees, single nodes, and paths that don't start from the root
  3. Draw out the parent-child relationships and verify the direction of value comparison
  4. Test with trees where no consecutive sequences exist (should return 1 if tree has nodes)
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

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

Load More