Lowest Common Ancestor
Question
Given a binary search tree (BST) | or Binary Tree, find the lowest common ancestor (LCA) node of two given nodes in the BST.
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

Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
Output: 6
Explanation: The LCA of nodes 2 and 8 is 6.

Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
Output: 2
Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
Input: root = [2,1], p = 2, q = 1
Output: 2
Solution
Given a binary tree (not binary search tree)
We can do a Tree DFS post-order traversal to start looking for the node. For example:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root: return None
if (root == p or root == q): return root
leftCommonAncesstor = self.lowestCommonAncestor(root.left, p, q)
rightCommonAncestor = self.lowestCommonAncestor(root.right, p, q)
if leftCommonAncesstor and rightCommonAncestor:
return root
return leftCommonAncesstor or rightCommonAncestor
The reason why we're doing postorder is because we need to look at the children first before making a decision.
In here, since the element in the tree are unique. We can return if root == p or root == q. So technically root can only equals to one of them because they're unqiue.
We check if in the left we have either node q or p and check in the right if we have the same.
If we have both left and right, we should return root
else we should return leftCommonAncesstor or rightCommonAncestor, the one which is not None will be returned
[!note]
We can do this since the question specifically mentioned that the root will guarantee exists.So technically we want to priority the top element we found. Therefore we use a postorder traversal
Time Complexity: $O(n)$ because we have to go through all the node one by one
Space Complexity: $O(logn)$ given the recursion, we need to traverse the height of the tree - which is $O(logn)$
Given a binary search tree
For a binary search tree, since left < root < right. It's actually faster and we can achieve $O(logn)$ time complexity.
For this, we can do a pre-order traversal, since we can determine if the root is the lowest common ancestor node without the need to traverse through its children.
Since everything on the right branch > root > everything on the left branch. We can simply do this check at a node to see if it's lowest common ancestor:
if (root.val <= p.val and root.val >= q.val) or\
(root.val >= p.val and root.val <= q.val):
# If root inbetween the 2 values
return root
There should be only 1 root that valid the above condition. There can't be a situation that some where in the tree there is another root that satisfy the same condition as well.
[!note]
This is because of the nature of binary search that if there is a root that stays in between 2 values. That root is the only root that is between that has 2 values.
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root: return None
if (root.val <= p.val and root.val >= q.val) or\
(root.val >= p.val and root.val <= q.val):
# If root inbetween the 2 values
return root
# root has to be either smaller or larger than both value, we can pick either q or p to check
if root.val > p.val:
# if root is larger, we need to go to the left
return self.lowestCommonAncestor(root.left, p, q)
else:
# else we go to the right
return self.lowestCommonAncestor(root.right, p, q)
We can also do this iteratively:
class Solution:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
currNode = root
while currNode:
if (currNode.val <= p.val and currNode.val >= q.val) or\
(currNode.val >= p.val and currNode.val <= q.val):
# If currNode inbetween the 2 values
return currNode
# currNode has to be either smaller or larger than both value, we can use 1 to check
if currNode.val > p.val:
# if currNode is larger, we need to go to the left
currNode = currNode.left
else:
# else we go to the right
currNode = currNode.right
Time complexity: $O(logn)$
Space complexity:
- Recursive method: $O(logn)$
- Iterative: $O(1)$