Lowest Common Ancestor (LCA) in a Binary Tree
Problem and examples
Given a binary tree and two nodes (p and q), find their lowest common ancestor (LCA). The LCA of two nodes p and q in a binary tree is defined as the lowest node in the tree that has both p and q as descendants (where a node can be a descendant of itself).
| Input Tree | Node 1 | Node 2 | LCA | Description |
|---|---|---|---|---|
| [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4] | 5 | 1 | 3 | LCA is 3 — the lowest node with both 5 and 1 in its subtrees |
| [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4] | 6 | 4 | 5 | LCA is 5 — 6 and 4 lie in the left subtree of 5 |
| [3, 5, 1, 6, 2, 0, 8, null, null, 7, 4] | 7 | 8 | 3 | LCA is 3 — 7 is in left subtree, 8 is in right subtree of root |
| [1, 2] | 1 | 2 | 1 | LCA is 1 — root node is the ancestor of 2 |
| [1, 2, 3, 4, 5, 6, 7] | 4 | 5 | 2 | LCA is 2 — both nodes are in the left subtree |
| [1, 2, 3, 4, 5, 6, 7] | 4 | 6 | 1 | LCA is 1 — nodes are in opposite subtrees of root |
| [1] | 1 | 1 | 1 | LCA is 1 — node is the ancestor of itself |
| [] | 1 | 2 | null | Tree is empty — no LCA can be found |
How it works
Understanding the Problem
We are given a binary tree and two distinct nodes, p and q. Our task is to find their Lowest Common Ancestor (LCA). The LCA is the deepest node in the tree that has both p and q as descendants (where a node can be a descendant of itself).
This problem is important in understanding tree structures and recursive thinking. For beginners, think of LCA as the “fork” in the tree where the paths to p and q split. We’ll solve this step-by-step using a real example and cover all possible cases.
Step-by-Step Solution with Example
Step 1: Analyze the Tree Structure
Let’s consider the following binary tree:
3
/ 5 1
/ / 6 2 0 8
/ 7 4
Suppose we need to find the LCA of nodes 5 and 1.
Step 2: Understand the Path to Each Node
Path to node 5: 3 → 5
Path to node 1: 3 → 1
The first common node from the root downwards is 3. So, the LCA is 3.
Step 3: Recursive Intuition
We use a recursive function that does the following:
- If the current node is null, return null.
- If the current node is either
porq, return the current node. - Recursively search left and right subtrees.
- If both left and right return non-null, current node is the LCA.
- If only one side is non-null, propagate it upwards.
Step 4: Java Code for LCA
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
return (left != null) ? left : right;
}
Step 5: Explanation for Beginners
We search down both subtrees. If both subtrees return a match, then the current node is where the paths to p and q diverge — this is the LCA. If only one subtree returns a match, we return that node up the recursive call stack.
Edge Cases
Case 1: Nodes in Different Subtrees
Example: nodes 5 and 1 → LCA is 3. This is the standard scenario.
Case 2: One Node is Ancestor of the Other
Example: nodes 5 and 4 → path is 5 → 2 → 4. Since 5 is an ancestor of 4, LCA is 5.
Case 3: Tree with Only Root and One Child
Example: tree [1,2], nodes 1 and 2 → LCA is 1. Even if the tree is tiny, LCA is still computed correctly.
Case 4: Tree is Empty
Example: tree [] → LCA is null. We should always handle this before running the main logic.
Case 5: p and q Are the Same Node
Example: nodes 2 and 2 → LCA is 2. A node is considered its own ancestor.
Finally
The LCA problem teaches recursive tree traversal and highlights important base conditions. Always understand how the tree is structured, and trace paths for both nodes. Use edge cases during testing to build intuition.
This problem is foundational for solving many other tree-based problems and is a must-know for interviews and practice.
- If
rootisnullor equal toporq, returnroot. - Recursively search for LCA in the left subtree:
left = lowestCommonAncestor(root.left, p, q). - Recursively search for LCA in the right subtree:
right = lowestCommonAncestor(root.right, p, q). - If both
leftandrightare non-null, thenrootis the LCA; returnroot. - If only one of
leftorrightis non-null, return the non-null value.
Implementation and efficiency
C implementation
#include <stdio.h>
#include <stdlib.h>
typedef struct TreeNode {
int val;
struct TreeNode *left, *right;
} TreeNode;
TreeNode* createNode(int val) {
TreeNode* node = (TreeNode*)malloc(sizeof(TreeNode));
node->val = val;
node->left = node->right = NULL;
return node;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
return left ? left : right;
}
int main() {
TreeNode* root = createNode(3);
root->left = createNode(5);
root->right = createNode(1);
root->left->left = createNode(6);
root->left->right = createNode(2);
root->right->left = createNode(0);
root->right->right = createNode(8);
root->left->right->left = createNode(7);
root->left->right->right = createNode(4);
TreeNode* lca = lowestCommonAncestor(root, root->left->right->left, root->left->right->right);
printf("LCA: %d\n", lca ? lca->val : -1);
return 0;
}C++ implementation
#include <iostream>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root;
return left ? left : right;
}
int main() {
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(5);
root->right = new TreeNode(1);
root->left->left = new TreeNode(6);
root->left->right = new TreeNode(2);
root->right->left = new TreeNode(0);
root->right->right = new TreeNode(8);
root->left->right->left = new TreeNode(7);
root->left->right->right = new TreeNode(4);
TreeNode* lca = lowestCommonAncestor(root, root->left->right->left, root->left->right->right);
cout << "LCA: " << (lca ? lca->val : -1) << endl;
return 0;
}Python implementation
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def lowestCommonAncestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowestCommonAncestor(root.left, p, q)
right = lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left if left else right
# Example usage
if __name__ == '__main__':
root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left.left = TreeNode(6)
root.left.right = TreeNode(2)
root.right.left = TreeNode(0)
root.right.right = TreeNode(8)
root.left.right.left = TreeNode(7)
root.left.right.right = TreeNode(4)
lca = lowestCommonAncestor(root, root.left.right.left, root.left.right.right)
print("LCA:", lca.val if lca else None)Java implementation
class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
public class Main {
public static TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) return root;
return left != null ? left : right;
}
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
root.left = new TreeNode(5);
root.right = new TreeNode(1);
root.left.left = new TreeNode(6);
root.left.right = new TreeNode(2);
root.right.left = new TreeNode(0);
root.right.right = new TreeNode(8);
root.left.right.left = new TreeNode(7);
root.left.right.right = new TreeNode(4);
TreeNode lca = lowestCommonAncestor(root, root.left.right.left, root.left.right.right);
System.out.println("LCA: " + (lca != null ? lca.val : "None"));
}
}JavaScript implementation
class TreeNode {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
}
}
function lowestCommonAncestor(root, p, q) {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left && right) return root;
return left ? left : right;
}
const root = new TreeNode(3);
root.left = new TreeNode(5);
root.right = new TreeNode(1);
root.left.left = new TreeNode(6);
root.left.right = new TreeNode(2);
root.right.left = new TreeNode(0);
root.right.right = new TreeNode(8);
root.left.right.left = new TreeNode(7);
root.left.right.right = new TreeNode(4);
const lca = lowestCommonAncestor(root, root.left.right.left, root.left.right.right);
console.log("LCA:", lca ? lca.val : "None");Go implementation
package main
import (
"fmt"
)
type TreeNode struct {
Val int
Left *TreeNode
Right *TreeNode
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
if root == nil || root == p || root == q {
return root
}
left := lowestCommonAncestor(root.Left, p, q)
right := lowestCommonAncestor(root.Right, p, q)
if left != nil && right != nil {
return root
}
if left != nil {
return left
}
return right
}
func main() {
root := &TreeNode{Val: 3}
root.Left = &TreeNode{Val: 5}
root.Right = &TreeNode{Val: 1}
root.Left.Left = &TreeNode{Val: 6}
root.Left.Right = &TreeNode{Val: 2}
root.Right.Left = &TreeNode{Val: 0}
root.Right.Right = &TreeNode{Val: 8}
root.Left.Right.Left = &TreeNode{Val: 7}
root.Left.Right.Right = &TreeNode{Val: 4}
lca := lowestCommonAncestor(root, root.Left.Right.Left, root.Left.Right.Right)
if lca != nil {
fmt.Println("LCA:", lca.Val)
} else {
fmt.Println("LCA: None")
}
}Rust implementation
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct TreeNode {
val: i32,
left: Option<Rc<RefCell<TreeNode>>>,
right: Option<Rc<RefCell<TreeNode>>>,
}
fn main() {
println!("Rust version would require lifetimes and Rc for shared ownership. This is a placeholder to show structure.");
}Kotlin implementation
class TreeNode(val value: Int) {
var left: TreeNode? = null
var right: TreeNode? = null
}
fun lowestCommonAncestor(root: TreeNode?, p: TreeNode?, q: TreeNode?): TreeNode? {
if (root == null || root == p || root == q) return root
val left = lowestCommonAncestor(root.left, p, q)
val right = lowestCommonAncestor(root.right, p, q)
return if (left != null && right != null) root else left ?: right
}
fun main() {
val root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left!!.left = TreeNode(6)
root.left!!.right = TreeNode(2)
root.right!!.left = TreeNode(0)
root.right!!.right = TreeNode(8)
root.left!!.right!!.left = TreeNode(7)
root.left!!.right!!.right = TreeNode(4)
val lca = lowestCommonAncestor(root, root.left!!.right!!.left, root.left!!.right!!.right)
println("LCA: ${lca?.value}")
}Swift implementation
class TreeNode {
var val: Int
var left: TreeNode?
var right: TreeNode?
init(_ val: Int) {
self.val = val
}
}
func lowestCommonAncestor(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
if root == nil || root === p || root === q {
return root
}
let left = lowestCommonAncestor(root?.left, p, q)
let right = lowestCommonAncestor(root?.right, p, q)
if left != nil && right != nil {
return root
}
return left ?? right
}
let root = TreeNode(3)
root.left = TreeNode(5)
root.right = TreeNode(1)
root.left?.left = TreeNode(6)
root.left?.right = TreeNode(2)
root.right?.left = TreeNode(0)
root.right?.right = TreeNode(8)
root.left?.right?.left = TreeNode(7)
root.left?.right?.right = TreeNode(4)
if let lca = lowestCommonAncestor(root, root.left?.right?.left, root.left?.right?.right) {
print("LCA: \(lca.val)")
} else {
print("LCA: None")
}TypeScript implementation
class TreeNode {
val: number;
left: TreeNode | null = null;
right: TreeNode | null = null;
constructor(val: number) {
this.val = val;
}
}
function lowestCommonAncestor(root: TreeNode | null, p: TreeNode, q: TreeNode): TreeNode | null {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
if (left && right) return root;
return left || right;
}
const root = new TreeNode(3);
root.left = new TreeNode(5);
root.right = new TreeNode(1);
root.left.left = new TreeNode(6);
root.left.right = new TreeNode(2);
root.right.left = new TreeNode(0);
root.right.right = new TreeNode(8);
root.left.right.left = new TreeNode(7);
root.left.right.right = new TreeNode(4);
const lca = lowestCommonAncestor(root, root.left.right.left!, root.left.right.right!);
console.log("LCA:", lca ? lca.val : "None");