Lowest Common Ancestor of Deepest Leaves
MEDIUMDescription
Given the root of a binary tree, return the lowest common ancestor of its deepest leaves.
Recall that:
- The node of a binary tree is a leaf if and only if it has no children
- The depth of the root of the tree is
0. if the depth of a node isd, the depth of each of its children isd + 1. - The lowest common ancestor of a set
Sof nodes, is the nodeAwith the largest depth such that every node inSis in the subtree with rootA.
Example 1:
Input: root = [3,5,1,6,2,0,8,null,null,7,4] Output: [2,7,4] Explanation: We return the node with value 2, colored in yellow in the diagram. The nodes coloured in blue are the deepest leaf-nodes of the tree. Note that nodes 6, 0, and 8 are also leaf nodes, but the depth of them is 2, but the depth of nodes 7 and 4 is 3.
Example 2:
Input: root = [1] Output: [1] Explanation: The root is the deepest node in the tree, and it's the lca of itself.
Example 3:
Input: root = [0,1,3,null,2] Output: [2] Explanation: The deepest leaf node in the tree is 2, the lca of one node is itself.
Constraints:
- The number of nodes in the tree will be in the range
[1, 1000]. 0 <= Node.val <= 1000- The values of the nodes in the tree are unique.
Note: This question is the same as 865: https://leetcode.com/problems/smallest-subtree-with-all-the-deepest-nodes/
Approaches
Checkout 2 different approaches to solve Lowest Common Ancestor of Deepest Leaves. Click on different approaches to view the approach and algorithm in detail.
Two-Pass Traversal
This approach tackles the problem by breaking it into two distinct, more straightforward subproblems. First, it performs a full traversal of the tree to identify all the leaf nodes that reside at the maximum possible depth. After collecting these leaves, it initiates a second traversal to find their lowest common ancestor (LCA).
Algorithm
- Pass 1: Find Deepest Leaves
- Initialize
maxDepth = -1and an empty listdeepestLeaves. - Define a recursive helper function
findDeepestLeaves(node, depth). - In the helper, if the node is a leaf, compare its
depthwithmaxDepth. Ifdepth > maxDepth, clear the list and add the current leaf. Ifdepth == maxDepth, just add the current leaf. - Traverse the entire tree by calling
findDeepestLeaves(root, 0).
- Initialize
- Pass 2: Find LCA of Deepest Leaves
- If the
deepestLeaveslist contains only one node, return that node as it's the LCA of itself. - Create a
Setof thedeepestLeavesfor efficient O(1) lookups. - Define another recursive helper function
findLCA(node, targets). - In this function, if the current
nodeis null or is one of the target leaves, return thenode. - Recursively call
findLCAfor the left and right children. - If the calls for both left and right subtrees return a non-null node, it means deepest leaves were found on both sides, making the current
nodethe LCA. Return the currentnode. - Otherwise, if only one side returned a non-null node, propagate that result up the recursion chain.
- The initial call
findLCA(root, targets)will return the final answer.
- If the
This method involves two main passes over the tree.
Pass 1: Identify the Deepest Leaves First, we need to determine the maximum depth of the tree and find all leaf nodes at that depth. This can be done with a single Depth-First Search (DFS) traversal.
- We maintain a global variable
maxDepthand a listdeepestLeaves. - We traverse the tree with a function
findDeepestLeaves(node, depth). - When we encounter a leaf node (
node.left == null && node.right == null), we check itsdepth.- If
depth > maxDepth, we've found a new deepest level. We updatemaxDepth, clear ourdeepestLeaveslist, and add the current leaf. - If
depth == maxDepth, we've found another leaf at the current maximum depth, so we add it to the list.
- If
Pass 2: Find the Lowest Common Ancestor (LCA) Once we have the set of deepest leaves, we find their LCA.
- If there's only one deepest leaf, it is its own LCA.
- Otherwise, we use a standard LCA-finding algorithm. A recursive function
findLCA(node, targets)is suitable. We convert our list of leaves to aSetfor efficient lookups. - This function returns the
nodeitself if it's one of the targets. It recursively checks its left and right subtrees. If it gets non-null results from both children, it means targets exist in both subtrees, making the currentnodethe LCA. If only one child returns a result, that result is propagated up.
import java.util.*;
class Solution {
private int maxDepth = -1;
private List<TreeNode> deepestLeaves = new ArrayList<>();
public TreeNode lcaDeepestLeaves(TreeNode root) {
// Pass 1: Find the deepest leaves
findDeepestLeaves(root, 0);
// If only one, it's the LCA
if (deepestLeaves.size() == 1) {
return deepestLeaves.get(0);
}
// Pass 2: Find the LCA of the collected leaves
Set<TreeNode> targets = new HashSet<>(deepestLeaves);
return findLCA(root, targets);
}
private void findDeepestLeaves(TreeNode node, int depth) {
if (node == null) {
return;
}
if (node.left == null && node.right == null) { // It's a leaf
if (depth > maxDepth) {
maxDepth = depth;
deepestLeaves.clear();
deepestLeaves.add(node);
} else if (depth == maxDepth) {
deepestLeaves.add(node);
}
return;
}
findDeepestLeaves(node.left, depth + 1);
findDeepestLeaves(node.right, depth + 1);
}
private TreeNode findLCA(TreeNode node, Set<TreeNode> targets) {
if (node == null || targets.contains(node)) {
return node;
}
TreeNode left = findLCA(node.left, targets);
TreeNode right = findLCA(node.right, targets);
if (left != null && right != null) {
return node;
}
return left != null ? left : right;
}
}
Complexity Analysis
Pros and Cons
- The logic is straightforward as it separates the problem into two well-known tree algorithms: finding nodes at a certain level and finding the LCA of a set of nodes.
- Requires two separate traversals of the tree, making it less efficient than a single-pass solution.
- Needs extra space to store all the deepest leaf nodes, which can be up to O(N) for a complete binary tree.
Code Solutions
Checking out 4 solutions in different languages for Lowest Common Ancestor of Deepest Leaves. Click on different languages to view the code.
/** * Definition for a binary tree node. * public class TreeNode { * public int val; * public TreeNode left; * public TreeNode right; * public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null) { * this.val = val; * this.left = left; * this.right = right; * } * } */ public class Solution { public TreeNode LcaDeepestLeaves ( TreeNode root ) { ( TreeNode , int ) Dfs ( TreeNode root ) { if ( root == null ) { return ( null , 0 ); } var l = Dfs ( root . left ); var r = Dfs ( root . right ); int d1 = l . Item2 ; int d2 = r . Item2 ; if ( d1 > d2 ) { return ( l . Item1 , d1 + 1 ); } if ( d1 < d2 ) { return ( r . Item1 , d2 + 1 ); } return ( root , d1 + 1 ); } return Dfs ( root ). Item1 ; } }Video Solution
Watch the video walkthrough for Lowest Common Ancestor of Deepest Leaves
Similar Questions
5 related questions you might find useful
Algorithms:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.