Table of contents
Given problem
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.
According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”
-
Example 1
- Input: root =
[3,5,1,6,2,0,8,null,null,7,4]
, p = 5, q = 1 - Output: 3
- Explanation: The LCA of nodes 5 and 1 is 3.
- Input: root =
-
Example 2
- Input: root =
[3,5,1,6,2,0,8,null,null,7,4]
, p = 5, q = 4 - Output: 5
- Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
- Input: root =
-
Example 3
- Input: root =
[1,2]
, p = 1, q = 2 - Output: 1
- Input: root =
-
Constraints
- The number of nodes in the tree is in the range
[2, 105]
. -109 <= Node.val <= 109
.- All Node.val are unique.
p != q
.p
andq
will exist in the tree.
- The number of nodes in the tree is in the range
Brute-force Solution
In the current brute-force solution, it has two points to get:
- The LCA node will have the same depth from the input nodes p and q.
- Define additional parent nodes to support get back to the root path.
So the TreeNode data structure will have additional two fields: parent
and depth
.
Below is the source code of this solution:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
this.preprocessTree(root, new TreeNode(Integer.MIN_VALUE));
return this.lca(p, q);
}
/**
* Calculation of the depth and the parent of each node
*
* @param root
*/
private void preprocessTree(TreeNode root, TreeNode parent) {
if (root == null) {
return;
}
root.depth = parent.depth + 1;
root.parent = parent;
preprocessTree(root.left, root);
preprocessTree(root.right, root);
}
/**
* Find the Lowest Common Ancestor Node of a tree
*
* @param p
* @param q
* @return
*/
private TreeNode lca(TreeNode p, TreeNode q) {
Objects.requireNonNull(p);
Objects.requireNonNull(q);
while (p.depth != q.depth) {
if (p.depth > q.depth) {
p = p.parent;
} else {
q = q.parent;
}
}
while (p != q) {
p = p.parent;
q = q.parent;
}
return p;
}
private TreeNode findNode(TreeNode root, int value) {
if (root == null) {
return null;
}
if (root.val == value) {
return root;
}
TreeNode tmp1 = findNode(root.left, value);
TreeNode tmp2 = findNode(root.right, value);
return tmp1 != null ? tmp1 : tmp2;
}
public static void main(String[] args) {
LowestCommonAncestor lca = new LowestCommonAncestor();
TreeNode root = buildExample1();
TreeNode p = lca.findNode(root, 5);
TreeNode q = lca.findNode(root, 4);
TreeNode res = lca.lowestCommonAncestor(root, p, q);
System.out.println("The LCA: " + res.val);
}
The complexity of this solution:
- Time complexity: O(n)
- Space complexity: O(n)
Optimized Solution 1
In this solution, we still use DFS to iterate all Tree’s nodes. But there are two things to note here:
- If we found the LCA node from both left and right sub-trees, we will return its parent node.
- Otherwise, we will return one of the LCA node of the left and right sub-trees when one of them is not equal to null.
Below is our Java source code for this solution:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {
return null;
}
if (root.val == p.val || root.val == q.val) {
return root;
}
TreeNode leftLcaNode = lowestCommonAncestor(root.left, p, q);
TreeNode rightLcaNode = lowestCommonAncestor(root.right, p, q);
if (leftLcaNode != null && rightLcaNode != null) {
return root;
}
return leftLcaNode != null ? leftLcaNode : rightLcaNode;
}
The complexity of this solution:
-
Time complexity: O(n) with n is the number of nodes.
The maximum number of node is 10^5. By default, we will consider a computer runs 10^8 operations per second.
So the time of this solution: 10^5 / 10^8 = 10^-3 = 1ms.
-
Space complexity: O(n) with n is the number of nodes because we need to maintain these nodes in recursive functions.
Wrapping up
Refer: