Longest Univalue Path
MedPrompt
Given the root of a binary tree, return the length of the longest path, where each node in the path has the same value. This path may or may not pass through the root.
The length of the path between two nodes is represented by the number of edges between them.
Example 1:
Input: root = [5,4,5,1,1,null,5]
Output: 2
Explanation: The shown image shows that the longest path of the same value (i.e. 5).Example 2:
Input: root = [1,4,5,4,4,null,5]
Output: 2
Explanation: The shown image shows that the longest path of the same value (i.e. 4).
Constraints:
- The number of nodes in the tree is in the range
[0, 104]. -1000 <= Node.val <= 1000- The depth of the tree will not exceed
1000.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach systematically considers every node in the tree as the potential 'apex' or 'root' of the longest univalue path. For each node, it calculates the length of the longest univalue path that can be formed by extending downwards into its left and right subtrees. The path length for a given apex node is the sum of the lengths of these two downward paths. The overall maximum length found after checking all nodes is the result. This method is straightforward but involves redundant calculations, as the path lengths for subtrees are computed multiple times.
Algorithm
- Initialize a global variable
maxLengthto 0. - Create a main function that traverses all nodes of the tree (e.g., using a preorder traversal). Let's call the traversal function
traverse(node). - Inside
traverse(node):- If
nodeis null, return. - Calculate the longest univalue path starting from
nodeand going down its left side. This requires a helper function, saypathLength(child, value). CallleftPath = pathLength(node.left, node.val). - Similarly, calculate
rightPath = pathLength(node.right, node.val). - Update the global maximum:
maxLength = max(maxLength, leftPath + rightPath). - Recursively call
traverse(node.left)andtraverse(node.right)to check all other nodes as potential apexes.
- If
- The helper function
pathLength(node, value):- If
nodeis null ornode.val != value, return 0. - Otherwise, it's a valid extension of the path. The length from this point is 1 (for the edge to this node) plus the length of the longest path from its children.
- Return
1 + max(pathLength(node.left, value), pathLength(node.right, value)).
- If
- The main function
longestUnivaluePath(root)will initializemaxLength, calltraverse(root), and returnmaxLength.
Walkthrough
The core idea is to iterate through all nodes. For each node, we find the longest univalue path starting from it and going down into the left subtree, and similarly for the right subtree. The sum of these two lengths gives a candidate for the longest univalue path. We keep track of the maximum candidate found.
Algorithm:
- Initialize a global variable
maxLengthto 0. - Define a main traversal function,
traverse(node), that visits every node in the tree (e.g., using preorder traversal). - For each
nodevisited bytraverse:- Treat
nodeas the apex. - Call a helper function,
pathLength(child, value), to find the length of the longest univalue path starting fromnode.leftwhere all nodes have the valuenode.val. Let this beleftPath. - Similarly, find
rightPathfornode.right. - Update the global maximum:
maxLength = Math.max(maxLength, leftPath + rightPath). - Continue the traversal:
traverse(node.left)andtraverse(node.right).
- Treat
- The
pathLength(node, value)helper function works as follows:- If
nodeis null ornode.valis not equal tovalue, it cannot be part of the path, so return 0. - Otherwise, it's a valid node. The path length is 1 (for the edge connecting to this node) plus the maximum length of univalue paths starting from its children.
- Return
1 + Math.max(pathLength(node.left, value), pathLength(node.right, value)).
- If
- The final answer is the value of
maxLengthafter the traversal is complete.
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { int maxLength = 0; public int longestUnivaluePath(TreeNode root) { if (root == null) { return 0; } traverse(root); return maxLength; } // Traverses every node to treat it as a potential apex private void traverse(TreeNode node) { if (node == null) { return; } // Calculate path length with 'node' as the apex int leftPath = pathLength(node.left, node.val); int rightPath = pathLength(node.right, node.val); maxLength = Math.max(maxLength, leftPath + rightPath); // Recurse to check other nodes as potential apexes traverse(node.left); traverse(node.right); } // Calculates the length of the longest downward univalue path from 'node' private int pathLength(TreeNode node, int value) { if (node == null || node.val != value) { return 0; } // 1 (for the edge to this node) + longest path from its children return 1 + Math.max(pathLength(node.left, value), pathLength(node.right, value)); }}Complexity
Time
O(N^2), where N is the number of nodes. The main `traverse` function visits each of the N nodes. For each node, the `pathLength` helper function is called, which may traverse the entire subtree below that node. In the worst case of a skewed tree, this leads to a quadratic number of operations.
Space
O(H), where H is the height of the tree, for the recursion stack. In the worst case of a skewed tree, H can be N, making the space complexity O(N).
Trade-offs
Pros
Conceptually simple and easy to follow.
Breaks the problem down by considering each node independently as the path's apex.
Cons
Highly inefficient due to redundant computations.
The
pathLengthfunction is called on the same subtrees multiple times, leading to a poor time complexity.
Solutions
Solution
/** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { private int ans ; public int longestUnivaluePath ( TreeNode root ) { ans = 0 ; dfs ( root ); return ans ; } private int dfs ( TreeNode root ) { if ( root == null ) { return 0 ; } int left = dfs ( root . left ); int right = dfs ( root . right ); left = root . left != null && root . left . val == root . val ? left + 1 : 0 ; right = root . right != null && root . right . val == root . val ? right + 1 : 0 ; ans = Math . max ( ans , left + right ); return Math . max ( left , right ); } }Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.