Facebook Pixel

549. Binary Tree Longest Consecutive Sequence II 🔒

Problem Description

You are given the root of a binary tree and need to find the length of the longest consecutive path in the tree.

A consecutive path consists of nodes where each consecutive pair of nodes has values that differ by exactly 1. The path can be either increasing (like [1,2,3,4]) or decreasing (like [4,3,2,1]). Mixed patterns like [1,2,4,3] are not valid consecutive paths.

The key aspect of this problem is that the path doesn't have to follow the traditional parent-to-child direction. Instead, it can go through any connected nodes in the tree, including paths that go child-parent-child. This means a valid path could start from a node in the left subtree, go up through the parent, and continue down into the right subtree, as long as the values remain consecutive.

For example, if you have a tree structure where a parent node has value 2, its left child has value 1, and its right child has value 3, then the path [1,2,3] going from left child through parent to right child would be a valid consecutive path of length 3.

The task is to find the maximum length among all such possible consecutive paths 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 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

  • Since we're dealing with a tree structure, the flowchart leads us directly to DFS (Depth-First Search).

Why DFS is the right choice:

  • We need to explore all possible paths in the tree to find the longest consecutive sequence
  • The path can go through child-parent-child relationships, requiring us to track information from subtrees
  • We need to return information from children to parents (increasing and decreasing consecutive counts)
  • DFS allows us to process each subtree completely before moving to the next, which is perfect for aggregating path information

Conclusion: The flowchart correctly identifies that for a tree-based problem like finding the longest consecutive path, DFS is the appropriate algorithm. The solution uses DFS to traverse the tree, maintaining both increasing and decreasing consecutive counts at each node, and combining them to find the maximum path length that could pass through that node.

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

Intuition

The key insight is that any consecutive path passing through a node can be broken down into two parts: an increasing sequence and a decreasing sequence that meet at that node.

Think about it this way - if we're standing at a node with value 5, a consecutive path through this node could look like [3,4,5,6,7]. This is essentially two segments: [3,4,5] (increasing to reach 5) and [5,6,7] (decreasing from 5's perspective, since child values are larger). The node with value 5 acts as a "bridge" connecting these two segments.

For each node, we need to track two pieces of information:

  1. The longest increasing consecutive path ending at this node (coming from its children)
  2. The longest decreasing consecutive path ending at this node (coming from its children)

Why track both? Because when we look at a parent node, what was "increasing" from a child's perspective becomes part of a different pattern. If a child has value 4 and parent has value 5, the child's increasing path contributes to the parent's decreasing path (since 4 + 1 = 5).

The brilliant part is combining these two counts at each node. If a node has an increasing path of length i and a decreasing path of length d, the maximum consecutive path passing through this node has length i + d - 1 (we subtract 1 because the node itself is counted in both paths).

This approach naturally handles the child-parent-child traversal requirement. When we compute the maximum path at each node by combining its increasing and decreasing sequences, we're essentially considering paths that go from one subtree, through the current node, and into another subtree - exactly what the problem asks for.

By using DFS to process each node bottom-up, we ensure that when we process a parent, we already have the complete information about the best consecutive paths in its children, allowing us to make optimal decisions at each step.

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

Solution Approach

The implementation uses a recursive DFS approach where each node returns a pair [incr, decr] representing the maximum increasing and decreasing consecutive paths ending at that node.

Base Case: When we encounter a None node, we return [0, 0] since there's no path through an empty node.

Recursive Processing: For each node, we:

  1. Initialize both incr and decr to 1 (the node itself forms a path of length 1)
  2. Recursively process left and right children to get their [incr, decr] values

Connecting with Left Child: If the left child exists:

  • If root.left.val + 1 == root.val: The left child's value is one less than current, so we can extend the left child's increasing path through current node. Set incr = i1 + 1
  • If root.left.val - 1 == root.val: The left child's value is one more than current, so we can extend the left child's decreasing path through current node. Set decr = d1 + 1

Connecting with Right Child: If the right child exists:

  • If root.right.val + 1 == root.val: Take the maximum of current incr and i2 + 1 (in case both children can form increasing paths)
  • If root.right.val - 1 == root.val: Take the maximum of current decr and d2 + 1 (in case both children can form decreasing paths)

Updating Global Maximum: At each node, we calculate the longest path passing through it as incr + decr - 1. This represents:

  • An increasing path from one subtree to current node (length incr)
  • A decreasing path from current node to another subtree (length decr)
  • Subtracting 1 because the current node is counted in both

We update the global ans variable with the maximum value seen so far.

Return Value: Each recursive call returns [incr, decr] to its parent, allowing the parent to potentially extend these consecutive paths further up the tree.

The final answer is the maximum path length found across all nodes in the tree.

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 small example to illustrate the solution approach.

Consider this binary tree:

       2
      / \
     1   3

Step 1: Process leaf node 1

  • Node value: 1
  • No children, so initialize incr = 1, decr = 1
  • Maximum path through this node: 1 + 1 - 1 = 1
  • Update global ans = 1
  • Return [1, 1] to parent

Step 2: Process leaf node 3

  • Node value: 3
  • No children, so initialize incr = 1, decr = 1
  • Maximum path through this node: 1 + 1 - 1 = 1
  • Global ans remains 1
  • Return [1, 1] to parent

Step 3: Process root node 2

  • Node value: 2
  • Initialize incr = 1, decr = 1
  • Left child returns [1, 1], right child returns [1, 1]

Connecting with left child (value 1):

  • Check: 1 + 1 = 2 ✓ (left child is one less than current)
  • This extends an increasing path: incr = 1 + 1 = 2
  • Check: 1 - 1 = 0 ≠ 2 (no decreasing path)

Connecting with right child (value 3):

  • Check: 3 + 1 = 4 ≠ 2 (no increasing path)
  • Check: 3 - 1 = 2 ✓ (right child is one more than current)
  • This extends a decreasing path: decr = 1 + 1 = 2

Calculate maximum path through node 2:

  • incr = 2 (path: 1→2)
  • decr = 2 (path: 2→3)
  • Maximum path: 2 + 2 - 1 = 3
  • This represents the complete path: 1→2→3
  • Update global ans = 3

Final Result: The longest consecutive path has length 3, representing the sequence [1,2,3] that goes from the left child, through the parent, to the right child.

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
8class Solution:
9    def longestConsecutive(self, root: TreeNode) -> int:
10        """
11        Find the longest consecutive path in a binary tree.
12        The path can be either increasing or decreasing.
13        """
14      
15        def dfs(node: TreeNode) -> tuple[int, int]:
16            """
17            Perform DFS to find longest increasing and decreasing paths.
18          
19            Args:
20                node: Current tree node
21              
22            Returns:
23                tuple: (longest_increasing_path, longest_decreasing_path) ending at current node
24            """
25            # Base case: empty node returns no path
26            if node is None:
27                return (0, 0)
28          
29            # Initialize increasing and decreasing paths starting from current node
30            increasing_length = 1
31            decreasing_length = 1
32          
33            # Recursively get path lengths from left and right subtrees
34            left_increasing, left_decreasing = dfs(node.left)
35            right_increasing, right_decreasing = dfs(node.right)
36          
37            # Check left child for consecutive sequences
38            if node.left:
39                # If left child's value is 1 less than current, extend decreasing path
40                if node.left.val + 1 == node.val:
41                    increasing_length = left_increasing + 1
42                # If left child's value is 1 more than current, extend increasing path
43                if node.left.val - 1 == node.val:
44                    decreasing_length = left_decreasing + 1
45          
46            # Check right child for consecutive sequences
47            if node.right:
48                # If right child's value is 1 less than current, extend decreasing path
49                if node.right.val + 1 == node.val:
50                    increasing_length = max(increasing_length, right_increasing + 1)
51                # If right child's value is 1 more than current, extend increasing path
52                if node.right.val - 1 == node.val:
53                    decreasing_length = max(decreasing_length, right_decreasing + 1)
54          
55            # Update global maximum with path through current node
56            # Subtract 1 to avoid counting current node twice
57            nonlocal max_length
58            max_length = max(max_length, increasing_length + decreasing_length - 1)
59          
60            return (increasing_length, decreasing_length)
61      
62        # Initialize maximum consecutive path length
63        max_length = 0
64      
65        # Start DFS from root
66        dfs(root)
67      
68        return max_length
69
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 length of consecutive sequence
18    private int maxLength;
19
20    /**
21     * Finds the longest consecutive sequence path in a binary tree.
22     * The path can go through any node and can be either increasing or decreasing.
23     * 
24     * @param root The root of the binary tree
25     * @return The length of the longest consecutive sequence
26     */
27    public int longestConsecutive(TreeNode root) {
28        maxLength = 0;
29        dfs(root);
30        return maxLength;
31    }
32
33    /**
34     * Performs depth-first search to find increasing and decreasing sequences.
35     * 
36     * @param root Current node being processed
37     * @return An array where [0] = max increasing sequence length ending at root,
38     *         [1] = max decreasing sequence length ending at root
39     */
40    private int[] dfs(TreeNode root) {
41        // Base case: null node returns [0, 0]
42        if (root == null) {
43            return new int[] {0, 0};
44        }
45      
46        // Initialize both increasing and decreasing lengths to 1 (current node only)
47        int increasingLength = 1;
48        int decreasingLength = 1;
49      
50        // Recursively process left and right subtrees
51        int[] leftResult = dfs(root.left);
52        int[] rightResult = dfs(root.right);
53      
54        // Process left child if it exists
55        if (root.left != null) {
56            // Check if left child forms an increasing sequence (left.val + 1 = root.val)
57            if (root.left.val + 1 == root.val) {
58                increasingLength = leftResult[0] + 1;
59            }
60            // Check if left child forms a decreasing sequence (left.val - 1 = root.val)
61            if (root.left.val - 1 == root.val) {
62                decreasingLength = leftResult[1] + 1;
63            }
64        }
65      
66        // Process right child if it exists
67        if (root.right != null) {
68            // Check if right child forms an increasing sequence
69            if (root.right.val + 1 == root.val) {
70                increasingLength = Math.max(increasingLength, rightResult[0] + 1);
71            }
72            // Check if right child forms a decreasing sequence
73            if (root.right.val - 1 == root.val) {
74                decreasingLength = Math.max(decreasingLength, rightResult[1] + 1);
75            }
76        }
77      
78        // Update global maximum with the path through current node
79        // Subtract 1 because the current node is counted in both sequences
80        maxLength = Math.max(maxLength, increasingLength + decreasingLength - 1);
81      
82        // Return the increasing and decreasing lengths for parent node to use
83        return new int[] {increasingLength, decreasingLength};
84    }
85}
86
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        maxLength = 0;
16        findLongestPath(root);
17        return maxLength;
18    }
19
20private:
21    int maxLength;
22  
23    /**
24     * DFS helper function to find the longest consecutive path
25     * @param node: current tree node
26     * @return: pair of {increasing path length, decreasing path length} ending at current node
27     */
28    vector<int> findLongestPath(TreeNode* node) {
29        // Base case: empty node returns {0, 0}
30        if (!node) {
31            return {0, 0};
32        }
33      
34        // Initialize both increasing and decreasing paths starting from current node
35        int increasingLength = 1;  // Length of increasing consecutive path ending at current node
36        int decreasingLength = 1;  // Length of decreasing consecutive path ending at current node
37      
38        // Recursively process left and right subtrees
39        vector<int> leftResult = findLongestPath(node->left);
40        vector<int> rightResult = findLongestPath(node->right);
41      
42        // Check left child for consecutive sequences
43        if (node->left) {
44            // If left child's value is one less than current node (increasing path)
45            if (node->left->val + 1 == node->val) {
46                increasingLength = leftResult[0] + 1;
47            }
48            // If left child's value is one more than current node (decreasing path)
49            if (node->left->val - 1 == node->val) {
50                decreasingLength = leftResult[1] + 1;
51            }
52        }
53      
54        // Check right child for consecutive sequences
55        if (node->right) {
56            // If right child's value is one less than current node (increasing path)
57            if (node->right->val + 1 == node->val) {
58                increasingLength = max(increasingLength, rightResult[0] + 1);
59            }
60            // If right child's value is one more than current node (decreasing path)
61            if (node->right->val - 1 == node->val) {
62                decreasingLength = max(decreasingLength, rightResult[1] + 1);
63            }
64        }
65      
66        // Update global maximum with the length of path passing through current node
67        // The total length is increasingLength + decreasingLength - 1 (subtract 1 to avoid counting current node twice)
68        maxLength = max(maxLength, increasingLength + decreasingLength - 1);
69      
70        // Return the lengths of increasing and decreasing paths ending at current node
71        return {increasingLength, decreasingLength};
72    }
73};
74
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// Global variable to track the maximum length found
16let maxLength: number = 0;
17
18/**
19 * Finds the longest consecutive sequence path in a binary tree
20 * @param root - The root node of the binary tree
21 * @returns The length of the longest consecutive sequence path
22 */
23function longestConsecutive(root: TreeNode | null): number {
24    // Reset the global maximum length
25    maxLength = 0;
26  
27    // Start DFS traversal to find the longest path
28    findLongestPath(root);
29  
30    return maxLength;
31}
32
33/**
34 * DFS helper function to find the longest consecutive path
35 * @param node - Current tree node being processed
36 * @returns Array containing [increasing path length, decreasing path length] ending at current node
37 */
38function findLongestPath(node: TreeNode | null): number[] {
39    // Base case: empty node returns [0, 0]
40    if (!node) {
41        return [0, 0];
42    }
43  
44    // Initialize both increasing and decreasing paths starting from current node
45    let increasingLength: number = 1;  // Length of increasing consecutive path ending at current node
46    let decreasingLength: number = 1;  // Length of decreasing consecutive path ending at current node
47  
48    // Recursively process left and right subtrees
49    const leftResult: number[] = findLongestPath(node.left);
50    const rightResult: number[] = findLongestPath(node.right);
51  
52    // Check left child for consecutive sequences
53    if (node.left) {
54        // If left child's value is one less than current node (increasing path)
55        if (node.left.val + 1 === node.val) {
56            increasingLength = leftResult[0] + 1;
57        }
58        // If left child's value is one more than current node (decreasing path)
59        if (node.left.val - 1 === node.val) {
60            decreasingLength = leftResult[1] + 1;
61        }
62    }
63  
64    // Check right child for consecutive sequences
65    if (node.right) {
66        // If right child's value is one less than current node (increasing path)
67        if (node.right.val + 1 === node.val) {
68            increasingLength = Math.max(increasingLength, rightResult[0] + 1);
69        }
70        // If right child's value is one more than current node (decreasing path)
71        if (node.right.val - 1 === node.val) {
72            decreasingLength = Math.max(decreasingLength, rightResult[1] + 1);
73        }
74    }
75  
76    // Update global maximum with the length of path passing through current node
77    // The total length is increasingLength + decreasingLength - 1 (subtract 1 to avoid counting current node twice)
78    maxLength = Math.max(maxLength, increasingLength + decreasingLength - 1);
79  
80    // Return the lengths of increasing and decreasing paths ending at current node
81    return [increasingLength, decreasingLength];
82}
83

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 left and right children exist
  • Comparing values between parent and children nodes
  • Updating the incr and decr counters
  • Computing the maximum value for ans

Since we visit each node once and perform O(1) operations 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:

  1. Recursion call stack: The DFS traversal uses recursion, and the maximum depth of the recursion stack equals the height of the tree. In the worst case (skewed tree), this could be O(n), while in the best case (balanced tree), it would be O(log n).

  2. Auxiliary space: The algorithm uses only a constant amount of extra variables (incr, decr, ans, i1, d1, i2, d2) at each recursive call, which doesn't affect the overall space complexity.

Therefore, the space complexity is O(h), which ranges from O(log n) for a balanced tree to O(n) for a completely unbalanced tree.

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

Common Pitfalls

Pitfall 1: Confusing Path Direction with Value Direction

A common mistake is misunderstanding the relationship between the path direction (increasing/decreasing) and how it connects through parent-child relationships. Many developers incorrectly assume:

  • An "increasing path" means going from parent to child with increasing values
  • A "decreasing path" means going from parent to child with decreasing values

The Reality: The terms "increasing" and "decreasing" refer to the consecutive sequence values, not the tree traversal direction. When processing a node:

  • If child.val + 1 == parent.val, the child contributes to an increasing path ending at parent
  • If child.val - 1 == parent.val, the child contributes to a decreasing path ending at parent

Example of the Confusion:

# WRONG interpretation
if node.left and node.left.val == node.val + 1:  # This seems "increasing"
    increasing_length = left_increasing + 1  # But this would be wrong!

Correct Understanding:

# CORRECT: When left child is ONE LESS than parent
if node.left and node.left.val + 1 == node.val:
    increasing_length = left_increasing + 1  # Extends increasing path through parent

Pitfall 2: Not Handling Both Children Properly

Another mistake is using simple assignment instead of max() when both children could contribute to the same type of path:

Wrong Approach:

if node.right and node.right.val + 1 == node.val:
    increasing_length = right_increasing + 1  # This overwrites left child's contribution!

Correct Approach:

if node.right and node.right.val + 1 == node.val:
    increasing_length = max(increasing_length, right_increasing + 1)  # Keeps the better path

Pitfall 3: Forgetting to Subtract 1 When Calculating Path Through Node

When calculating the total path length passing through a node, developers often forget that the current node is counted in both the increasing and decreasing segments:

Wrong:

max_length = max(max_length, increasing_length + decreasing_length)  # Counts node twice!

Correct:

max_length = max(max_length, increasing_length + decreasing_length - 1)  # Accounts for overlap

Solution to Avoid These Pitfalls:

  1. Draw out examples: Trace through small examples manually, labeling each node with its [incr, decr] values
  2. Think from child's perspective: When at a parent node, think "what value must my child have for me to extend its path?"
  3. Test edge cases: Single node tree, all same values, strictly increasing/decreasing paths
  4. Verify the math: Always remember that joining two paths through a node means that node appears in both counts
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following array represent a max heap?


Recommended Readings

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

Load More