Skip to content

Lowest Common Ancestor of a Binary Tree

Given a binary tree, find the lowest common ancestor (LCA) of two given nodes p and q in the tree.

The lowest common ancestor is the deepest node in the tree that is an ancestor of both p and q. A node can be an ancestor of itself.

InputpqOutputExplanation
[3,5,1,6,2,0,8,null,null,7,4]5133 is the LCA of 5 and 1
[3,5,1,6,2,0,8,null,null,7,4]5455 is the LCA of 5 and 4 (5 is ancestor of 4)
[1,2]1211 is the LCA of 1 and 2
Example 1: 3 LCA(5, 1) = 3
/ \
5 1
/ \ / \
6 2 0 8
/ \
7 4
Example 2: LCA(5, 4) = 5 (same tree, 5 is ancestor of 4)
Example 3: 1 LCA(1, 2) = 1 (1 is ancestor of itself and 2)
\
2
  • The number of nodes in the tree is in the range [2, 10^5].
  • -10^9 <= Node.val <= 10^9
  • All nodes have unique values.
  • p and q will exist in the tree.
ApproachTimeSpaceBest When
DFS RecursiveO(n)O(h)Simple to implement; space is the call stack
Parent Pointers + Hash SetO(n)O(h)If multiple LCA queries needed; explicit space usage

Where h = height (at most n)

  • Pick DFS Recursive if you want the simplest, most elegant solution that naturally leverages tree structure.
  • Pick Parent Pointers + Hash Set if you want to understand how to track ancestry and use hash sets for quick lookups.
Simplest
DFS Recursive
O(n) time · O(h) space
Explicit Ancestry
Parent Pointers + Hash Set
O(n) time · O(h) space
★ Recommended

The key insight: the LCA of p and q is the deepest node where p and q are in different subtrees (or one is the node itself).

For each node, recursively search for p and q in its left and right subtrees:

  • If both are found in different subtrees, this node is the LCA.
  • If both are in one subtree, recurse into that subtree.
  • If found in a subtree, return that node to propagate up.
⏱ Time O(n) Visit all nodes in worst case 💾 Space O(h) Recursion call stack

Input: Tree with nodes [3,5,1,6,2,0,8], p = 5, q = 1

3
/ \
5 1
/ \ / \
6 2 0 8
NodeLeft ResultRight ResultDecisionReason
6nullnullnullLeaf, neither p nor q
2nullnullnullLeaf, neither p nor q
5p found2 (not q)return 5Left subtree has p
0nullnullnullLeaf, neither p nor q
8nullnullnullLeaf, neither p nor q
10 (not p)8 (not q)return 1This is q itself
35 (has p)1 (has q)return 3Both in different subtrees

Animated walkthrough

How DFS finds the lowest common ancestor

Use Next or Autoplay to watch as recursion explores subtrees and identifies where p and q meet.

Step 1 of 4
Waiting to begin...
Array state
Memory / lookup state

function lca(root, p, q):
if root is null:
return null
if root is p or root is q:
return root
left = lca(root.left, p, q)
right = lca(root.right, p, q)
if left is not null and right is not null:
return root // Both found in different subtrees
return left if left is not null else right // One subtree has both
lowest_common_ancestor_of_a_binary_tree_dfs.py
from typing import Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def lowestCommonAncestor(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> TreeNode:
"""
Find the lowest common ancestor of two nodes in a binary tree using DFS.
Time Complexity: O(n) where n is the number of nodes
Space Complexity: O(h) where h is the height (call stack depth)
"""
if root is None:
return None
# If either p or q is the current node, it's a potential LCA
if root == p or root == q:
return root
# Search in left and right subtrees
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
# If both p and q are found in different subtrees, root is LCA
if left is not None and right is not None:
return root
# If both are in one subtree, return that subtree's result
return left if left is not None else right
# Test cases
if __name__ == "__main__":
# Example 1: [3,5,1,6,2,0,8,null,null,7,4]
# 3
# / \
# 5 1
# / \ / \
# 6 2 0 8
# / \
# 7 4
root1 = TreeNode(3)
root1.left = TreeNode(5)
root1.right = TreeNode(1)
root1.left.left = TreeNode(6)
root1.left.right = TreeNode(2)
root1.right.left = TreeNode(0)
root1.right.right = TreeNode(8)
root1.left.right.left = TreeNode(7)
root1.left.right.right = TreeNode(4)
p1 = root1.left # Node 5
q1 = root1.right # Node 1
result1 = lowestCommonAncestor(root1, p1, q1)
print(f"LCA of {p1.val} and {q1.val}: {result1.val}") # Expected: 3
# Example 2: Same tree, p=5, q=4
p2 = root1.left # Node 5
q2 = root1.left.right.right # Node 4
result2 = lowestCommonAncestor(root1, p2, q2)
print(f"LCA of {p2.val} and {q2.val}: {result2.val}") # Expected: 5
# Example 3: [1,2]
# 1
# \
# 2
root3 = TreeNode(1)
root3.right = TreeNode(2)
p3 = root3 # Node 1
q3 = root3.right # Node 2
result3 = lowestCommonAncestor(root3, p3, q3)
print(f"LCA of {p3.val} and {q3.val}: {result3.val}") # Expected: 1
🎯 Interview Favourite

Store parent pointers for each node during a DFS traversal. Then, for node p, collect all ancestors in a hash set. Walk up from q using parent pointers until finding a node in the set.

This approach separates the traversal (finding nodes and storing parents) from the LCA search.

⏱ Time O(n) One DFS to find parents, then walk ancestors 💾 Space O(h) Hash set of ancestors

Input: Tree with nodes [3,5,1,6,2,0,8], p = 5, q = 1

Step 1: Find p and store all ancestors of p:

DFS traversal finds p=5
Ancestors of 5: {5, 3} // 5 itself and its ancestor 3

Step 2: Walk from q upward, check each ancestor against the set:

Walk from q=1:
Check 1 → not in {5, 3}
Check parent(1)=3 → found in set!
LCA is 3 ✓
function lca(root, p, q):
parent = {}
visited = set()
// DFS to store parent pointers
function dfs(node):
if node is null:
return
visited.add(node)
if node.left is not null:
parent[node.left] = node
dfs(node.left)
if node.right is not null:
parent[node.right] = node
dfs(node.right)
dfs(root)
// Collect all ancestors of p
ancestors = set()
current = p
while current is not null:
ancestors.add(current)
current = parent.get(current)
// Walk from q up to find first ancestor in set
current = q
while current not in ancestors:
current = parent.get(current)
return current
lowest_common_ancestor_of_a_binary_tree_parent_pointers.py
from typing import Optional
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def lowestCommonAncestor(root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> TreeNode:
"""
Find the lowest common ancestor using parent pointers and hash set.
Time Complexity: O(n) where n is the number of nodes
Space Complexity: O(h) where h is the height
"""
parent = {}
visited = set()
def dfs(node):
"""Store parent pointers for all nodes."""
if node is None:
return
visited.add(node)
if node.left:
parent[node.left] = node
dfs(node.left)
if node.right:
parent[node.right] = node
dfs(node.right)
# Build parent pointers
dfs(root)
# Collect all ancestors of p
ancestors = set()
current = p
while current:
ancestors.add(current)
current = parent.get(current)
# Walk from q up to find first common ancestor
current = q
while current not in ancestors:
current = parent.get(current)
return current
# Test cases
if __name__ == "__main__":
# Example 1: [3,5,1,6,2,0,8,null,null,7,4]
# 3
# / \
# 5 1
# / \ / \
# 6 2 0 8
# / \
# 7 4
root1 = TreeNode(3)
root1.left = TreeNode(5)
root1.right = TreeNode(1)
root1.left.left = TreeNode(6)
root1.left.right = TreeNode(2)
root1.right.left = TreeNode(0)
root1.right.right = TreeNode(8)
root1.left.right.left = TreeNode(7)
root1.left.right.right = TreeNode(4)
p1 = root1.left # Node 5
q1 = root1.right # Node 1
result1 = lowestCommonAncestor(root1, p1, q1)
print(f"LCA of {p1.val} and {q1.val}: {result1.val}") # Expected: 3
# Example 2: Same tree, p=5, q=4
p2 = root1.left # Node 5
q2 = root1.left.right.right # Node 4
result2 = lowestCommonAncestor(root1, p2, q2)
print(f"LCA of {p2.val} and {q2.val}: {result2.val}") # Expected: 5
# Example 3: [1,2]
# 1
# \
# 2
root3 = TreeNode(1)
root3.right = TreeNode(2)
p3 = root3 # Node 1
q3 = root3.right # Node 2
result3 = lowestCommonAncestor(root3, p3, q3)
print(f"LCA of {p3.val} and {q3.val}: {result3.val}") # Expected: 1