549. Binary Tree Longest Consecutive Sequence II
Problem Description
This problem provides us with the root of a binary tree and asks us to find the length of the longest consecutive path in that tree. The consecutive path we are looking for can be either increasing or decreasing in terms of node values, and every two consecutive nodes in this path should have values that differ by exactly one. To clarify, paths such as [1,2,3,4]
(increasing) and [4,3,2,1]
(decreasing) are valid. However, a path like [1,2,4,3]
is invalid as the values do not differ by one. Additionally, it's important to note that the path does not need to follow parent-child relationships and could include a 'bounce', going from child to parent to another child (child-parent-child pattern).
Flowchart Walkthrough
To find the appropriate algorithm for solving LeetCode 549. Binary Tree Longest Consecutive Sequence II, let's walk through the decision process using the Flowchart. Here's a detailed analysis based on the graph nodes and edges:
-
Is it a graph?
- Yes: The binary tree is a type of graph.
-
Is it a tree?
- Yes: A binary tree is specifically a tree structure.
-
DFS (Depth-First Search)
- Since we've determined it is a tree, the next step suggests using a Depth-First Search (DFS) pattern.
The problem involves finding the longest consecutive sequence that can increase or decrease, requiring traversal where each path needs to be checked for its consecutive nature. DFS is particularly well-suited for such tree problems because it efficiently explores all possible paths and tracks conditions (like consecutive sequences) deeply before backtracking.
Conclusion: The flowchart guides us to utilize the Depth-First Search algorithm to solve the problem due to its efficiency in handling tree structures and ability to manage path-dependent conditions.
Intuition
The intuition behind this solution lies in using a recursive depth-first search (DFS) algorithm to traverse the tree and compute the increasing and decreasing consecutive paths. For every node, there can be four kinds of consecutive paths:
- Paths that only include this node.
- Paths that start from this node and extend to any node in the left subtree.
- Paths that start from this node and extend to any node in the right subtree.
- Paths that go through this node, meaning they start from the left subtree, include this node, and go to the right subtree.
To track these paths, for each node, we calculate two values: incr
and decr
, which represent the lengths of the longest increasing and decreasing paths ending at this node, respectively.
When we visit a node, we check its children. If either child's value is one more than the current node's value, we increment incr
; if it's one less, we increment decr
. We take these two values from both children and use them to update the incr
and decr
values of the current node.
The magic happens by considering not only the deepest path from this node to each child but also the possibility of continuing the path by "bouncing" from one child to the other through the current node, which effectively can increase the overall length of the consecutive path.
As we compute the incr
and decr
values for each node, we keep track of the global maximum length (ans
) found so far. To get the maximum consecutive path that can pass through a node, we add the incr
and decr
values of this node but subtract 1 because the node itself is counted twice (once in each path). After the DFS traverses the whole tree, ans
will store the length of the longest consecutive path.
Learn more about Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The solution uses a recursive depth-first search (DFS) to explore the binary tree and calculate the length of the longest consecutive path. Here's a step-by-step walkthrough of the implementation:
-
Recursive Function Definition: A function
dfs
is defined, which takes a node of the tree as an argument and returns a tuple[incr, decr]
.incr
holds the maximum length of an increasing consecutive path ending at that node, anddecr
is the same for decreasing paths. -
Base Case: When
dfs
encounters aNone
(indicating the node being visited is non-existent or a leaf's child), it returns[0, 0]
as there are no consecutive paths beyond this point. -
State Variables: The solution introduces a nonlocal variable
ans
to track the maximum length found during traversal. -
Child Nodes Analysis: Each call to
dfs
considers the current node's left and right child nodes. For each child, the function computesi1/d1
andi2/d2
which are tuples returned by the recursivedfs
call on the left and right children, respectively. -
Update Increments/Decrements:
- It then checks the value of the left child (if it exists), updating
incr
ordecr
based on whether the left child's value is one less or one more than the current node's value, respectively. - Similarly, checks are performed on the right child, updating
incr
anddecr
by comparing the values of the right child and the current node.
- It then checks the value of the left child (if it exists), updating
-
Global Maximum Update: After calculating the
incr
anddecr
for both the left and right children,ans
is updated by taking the sum of the current node'sincr
anddecr
minus one — as the current node is counted in bothincr
anddecr
and should only be counted once. -
Return Values: Finally, the function returns a tuple
[incr, decr]
for the current node, which signifies the maximum lengths of consecutive paths ending at this node (both increasing and decreasing). -
Result: After
dfs
is called on the root,ans
contains the length of the longest consecutive path in the tree, which is then returned as the result of thelongestConsecutive
function.
The algorithm effectively scans the entire tree only once, ensuring an efficient solution with a time complexity of O(n), where n is the number of nodes in the tree. By considering each node and its potential paths (both increasing and decreasing), as well as potential "bounces" between children, we ensure no possible paths are missed.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example to illustrate the solution approach. Consider the following binary tree:
3 / \ 2 4 / \ 1 5
Here's how the recursive DFS would process this tree:
-
Starting at the Root (Node 3): The initial call to DFS is made on the root (node 3). At this point,
ans
is initialized to 0. -
Recursive Calls: The
dfs
function will make recursive calls to the left (node 2) and right (node 4) children:- Left Child (Node 2):
- The left child has the value 2, which is less by 1 than its parent (node 3), so for node 3,
decr = 2
. - It recursively calls
dfs
on node 2's left child (node 1). - Node 1 is also less by 1 than node 2, so now for node 2,
decr = 2
. - Node 1 has no children, so the recursive call would return
[0, 0]
. - The
decr
from node 2 is now combined with node 3'sdecr
to updateans
, if necessary, toans = decr_2 + decr_3 - 1
.
- The left child has the value 2, which is less by 1 than its parent (node 3), so for node 3,
- Right Child (Node 4):
- The right child has the value 4, which is more by 1 than its parent (node 3), so for node 3,
incr = 2
. - It recursively calls
dfs
on node 4's right child (node 5). - Node 5 is more by 1 than node 4, so for node 4,
incr = 2
. - The
incr
from node 4 is now combined with node 3'sincr
to updateans
, if necessary, toans = incr_4 + incr_3 - 1
.
- The right child has the value 4, which is more by 1 than its parent (node 3), so for node 3,
- Left Child (Node 2):
-
Update Global Maximum (
ans
): Since both left and right children of node 3 form consecutive sequences, we calculate the maximum sum ofdecr
andincr
.- From left child's
decr
(2 from node 2) and right child'sincr
(2 from node 4), we get2 + 2 - 1 = 3
for node 3, which means including node 3, there is a path of length 3 that goes from node 5 to node 3 to node 2.
- From left child's
-
Finalizing the Result: After the full traversal,
- We have computed
incr
anddecr
for all nodes. - We've also computed
ans
at each node, which is the maximum value obtained by addingincr
anddecr
and subtracting 1 (to account for the current node being included in both sequences). - Since the tree was recursively traversed,
ans
holds the maximum length of the longest consecutive path after taking all nodes into consideration.
- We have computed
Based on our example, the longest consecutive path is 3 (which is the path [2, 3, 4]
or [4, 3, 2]
), and this is output by the longestConsecutive
function as the DFS is executed from the root of the tree to all its children and their subsequent descendants.
Solution Implementation
1# Definition for a binary tree node.
2class TreeNode:
3 def __init__(self, val=0, left=None, right=None):
4 self.val = val
5 self.left = left
6 self.right = right
7
8
9class Solution:
10 def longestConsecutive(self, root: TreeNode) -> int:
11 # Recursive depth-first search to find the longest consecutive path
12 def dfs(node):
13 if node is None:
14 return [0, 0] # Base case: return 0's for the length of increasing and decreasing sequences
15
16 nonlocal max_length # Use nonlocal keyword to modify the non-local max_length variable
17 incr_length = decr_length = 1 # Initialize lengths of increasing and decreasing paths
18
19 # Perform DFS on the left and right children
20 left_incr, left_decr = dfs(node.left)
21 right_incr, right_decr = dfs(node.right)
22
23 # Check for consecutive increments or decrements on the left child
24 if node.left:
25 if node.left.val + 1 == node.val:
26 incr_length = left_incr + 1
27 elif node.left.val - 1 == node.val:
28 decr_length = left_decr + 1
29
30 # Check for consecutive increments or decrements on the right child
31 if node.right:
32 if node.right.val + 1 == node.val:
33 incr_length = max(incr_length, right_incr + 1)
34 elif node.right.val - 1 == node.val:
35 decr_length = max(decr_length, right_decr + 1)
36
37 # Update the max_length considering both increasing and decreasing paths
38 max_length = max(max_length, incr_length + decr_length - 1)
39 return [incr_length, decr_length]
40
41 max_length = 0 # Initialize the maximum length of consecutive sequence
42 dfs(root) # Start DFS from the root
43 return max_length # Return the maximum length found
44
1class Solution {
2 private int longestLength;
3
4 // Function to start the longest consecutive sequence process
5 public int longestConsecutive(TreeNode root) {
6 longestLength = 0;
7 dfs(root);
8 return longestLength;
9 }
10
11 // Perform a Depth First Search on the tree
12 private int[] dfs(TreeNode node) {
13 if (node == null) {
14 return new int[] {0, 0};
15 }
16
17 int incrementing = 1; // Length of incrementing sequence ending at this node
18 int decrementing = 1; // Length of decrementing sequence ending at this node
19
20 // Recurse left
21 int[] leftSubtree = dfs(node.left);
22 // Recurse right
23 int[] rightSubtree = dfs(node.right);
24
25 // Check left child
26 if (node.left != null) {
27 if (node.left.val + 1 == node.val) {
28 incrementing = leftSubtree[0] + 1;
29 }
30 if (node.left.val - 1 == node.val) {
31 decrementing = leftSubtree[1] + 1;
32 }
33 }
34
35 // Check right child
36 if (node.right != null) {
37 if (node.right.val + 1 == node.val) {
38 incrementing = Math.max(incrementing, rightSubtree[0] + 1);
39 }
40 if (node.right.val - 1 == node.val) {
41 decrementing = Math.max(decrementing, rightSubtree[1] + 1);
42 }
43 }
44
45 // Update longestLength if the current sequence is the longest
46 // -1 is to not double count the current node in both incrementing and decrementing sequences
47 longestLength = Math.max(longestLength, incrementing + decrementing - 1);
48
49 // Return the length of the longest incrementing and decrementing sequence ending at this node
50 return new int[] {incrementing, decrementing};
51 }
52}
53
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 longestStreak;
15
16 // Function that starts the process and returns the longest consecutive sequence length
17 int longestConsecutive(TreeNode* root) {
18 longestStreak = 0;
19 dfs(root);
20 return longestStreak;
21 }
22
23 // Helper function to perform DFS and calculate the consecutive sequence length
24 vector<int> dfs(TreeNode* root) {
25 // Base case: if the node is null, return {0, 0} as there is no sequence
26 if (!root) return {0, 0};
27
28 // Initialize the length of the increasing and decreasing sequences to 1 (the root itself)
29 int increaseLength = 1, decreaseLength = 1;
30
31 // Recursively call dfs for the left and right subtrees
32 vector<int> leftSequence = dfs(root->left);
33 vector<int> rightSequence = dfs(root->right);
34
35 // Process left child
36 if (root->left) {
37 // Check if it's consecutively increasing
38 if (root->left->val + 1 == root->val) increaseLength = leftSequence[0] + 1;
39 // Check if it's consecutively decreasing
40 if (root->left->val - 1 == root->val) decreaseLength = leftSequence[1] + 1;
41 }
42
43 // Process right child
44 if (root->right) {
45 // Check if it's consecutively increasing
46 if (root->right->val + 1 == root->val) increaseLength = max(increaseLength, rightSequence[0] + 1);
47 // Check if it's consecutively decreasing
48 if (root->right->val - 1 == root->val) decreaseLength = max(decreaseLength, rightSequence[1] + 1);
49 }
50
51 // Update the longest streak result by taking the maximum sum of increasing and
52 // decreasing lengths from the current node minus 1 (to avoid double-counting the node itself)
53 longestStreak = max(longestStreak, increaseLength + decreaseLength - 1);
54
55 // Return a pair of the longest increasing and decreasing sequences starting from the current node
56 return {increaseLength, decreaseLength};
57 }
58};
59
1// Definition for a binary tree node.
2class TreeNode {
3 val: number;
4 left: TreeNode | null;
5 right: TreeNode | null;
6
7 constructor(val: number = 0, left: TreeNode | null = null, right: TreeNode | null = null) {
8 this.val = val;
9 this.left = left;
10 this.right = right;
11 }
12}
13
14let longestStreak: number;
15
16// Function that starts the process and returns the longest consecutive sequence length
17function longestConsecutive(root: TreeNode | null): number {
18 longestStreak = 0;
19 dfs(root);
20 return longestStreak;
21}
22
23// Helper function to perform DFS and calculate the consecutive sequence length
24function dfs(root: TreeNode | null): number[] {
25 // Base case: if the node is null, return [0, 0] as there is no sequence
26 if (!root) return [0, 0];
27
28 // Initialize the length of the increasing and decreasing sequences to 1 (the root itself)
29 let increaseLength: number = 1, decreaseLength: number = 1;
30
31 // Recursively call dfs for the left and right subtrees
32 const leftSequence: number[] = dfs(root.left);
33 const rightSequence: number[] = dfs(root.right);
34
35 // Process left child
36 if (root.left) {
37 // Check if it's consecutively increasing
38 if (root.left.val + 1 === root.val) {
39 increaseLength = leftSequence[0] + 1;
40 }
41 // Check if it's consecutively decreasing
42 if (root.left.val - 1 === root.val) {
43 decreaseLength = leftSequence[1] + 1;
44 }
45 }
46
47 // Process right child
48 if (root.right) {
49 // Check if it's consecutively increasing
50 if (root.right.val + 1 === root.val) {
51 increaseLength = Math.max(increaseLength, rightSequence[0] + 1);
52 }
53 // Check if it's consecutively decreasing
54 if (root.right.val - 1 === root.val) {
55 decreaseLength = Math.max(decreaseLength, rightSequence[1] + 1);
56 }
57 }
58
59 // Update the longest streak result by taking the maximum sum of increasing and
60 // decreasing lengths from the current node minus 1 (to avoid double-counting the node)
61 longestStreak = Math.max(longestStreak, increaseLength + decreaseLength - 1);
62
63 // Return a pair of the longest increasing and decreasing sequences starting from the current node
64 return [increaseLength, decreaseLength];
65}
66
Time and Space Complexity
Time Complexity
The provided code executes a depth-first search (DFS) on a binary tree. For every node, it calculates the longest consecutive sequence that can be formed both increasing and decreasing. The decision to increment or decrement the consecutive count, or to reset it, is done at every node. This leads to each node being visited exactly once.
Therefore, the time complexity of the DFS is O(N)
, where N
is the number of nodes in the tree. This is because the function processes each node a single time without revisiting anything.
Space Complexity
The space complexity of the algorithm includes the space used by the recursive call stack during the DFS traversal as well as the space used for storing the variables in each recursive call.
In the worst case, the height of the binary tree may be O(N)
(in case of a skewed tree where each node has only one child), which would imply O(N)
recursive calls stack space would be used. For balanced trees, the average case height would be O(log N)
leading to an average case space complexity of O(log N)
.
Hence, the space complexity is O(N)
in the worst case, but in the average case for a balanced tree, it would be O(log N)
.
The ans
variable used to store the maximum length of the consecutive sequence doesn't significantly affect the space complexity as it is a single integer value.
Learn more about how to find time and space complexity quickly using problem constraints.
How many times is a tree node visited in 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 algomonster s3 us east 2 amazonaws com 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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https algomonster s3 us east 2 amazonaws com 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!