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

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

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)
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?


Recommended Readings

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

Load More