Lowest Common Ancestor of a Binary Tree

MediumBinary Tree · Post-Order DFS🧩 Tree DFSLeetCode #236 ↗Updated

Problem

Given a binary tree (not a BST) and two distinct nodes p and q that are both in it, return their lowest common ancestor: the deepest node that has both of them in its subtree. A node counts as an ancestor of itself.
Input[3,5,1,6,2,0,8], p=5, q=1Output3
Input[3,5,1,6,2,0,8], p=5, q=6Output5
Input[1,2], p=1, q=2Output1

2 ≤ n ≤ 105  ·  all values unique  ·  p ≠ q  ·  p and q exist in the tree

Try it yourself

Java
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Ask each subtree “did you see p or q?”. The first node (bottom-up) where the answer is yes from both sides — or that is itself p/q — is the LCA.
PatternTree DFS

Instinct If the answer for a node can be built from the answers of its left and right subtrees, write one recursive function and trust it on the children.

Memory hack Ask the kids, then decide - each child reports up, the parent combines, and a global variable keeps the best "bend" seen so far.

Open the Tree DFS guide →

Approaches

Brute ForceTop-Down with Subtree Search

Time O(n²)Space O(h)

At each node, search the left subtree for p and q. Both on the left → go left; neither → go right; split → this node is the LCA. Each step re-scans a whole subtree, so a skewed tree costs O(n²).

Java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == p || root == q) return root;
    boolean pLeft = contains(root.left, p), qLeft = contains(root.left, q);
    if (pLeft && qLeft) return lowestCommonAncestor(root.left, p, q);
    if (!pLeft && !qLeft) return lowestCommonAncestor(root.right, p, q);
    return root; // p and q are on different sides
}
private boolean contains(TreeNode node, TreeNode target) {
    if (node == null) return false;
    return node == target || contains(node.left, target) || contains(node.right, target);
}

Easy to justify, but searches the same nodes again at every level.

At a glance

LevelApproachTimeSpace
Brute ForceTop-Down with Subtree SearchO(n²)O(h)
BetterParent Pointers + Ancestor SetO(n)O(n)
OptimalRecursive Post-OrderO(n)O(h)
Common pitfalls
  • Comparing values with a BST rule (p.val < root.val) — this tree is not ordered.
  • Forgetting that a node can be its own ancestor (p is an ancestor of q).
  • Iterative version: walking up q before the parent map contains both nodes.
Interview tips
  • Ask: BST or plain tree? Are p and q guaranteed to exist?
  • Explain the three return cases: both non-null, one non-null, none.
  • Offer the parent-pointer version if recursion depth (skewed, n=105) is a concern.
Pattern
Post-order DFS returning a “found” signal → combine evidence from both subtrees to locate a meeting node.
My notes saved in this browser