Introduction
In our last lesson, you mastered the binary heap, implementing it from scratch and using it to solve the "Kth largest element" problem efficiently. This honed your understanding of priority queues and the crucial sift-up and sift-down operations.
Today, we continue our exploration of tree algorithms by tackling another quintessential interview problem: finding the Lowest Common Ancestor (LCA). Your learning outcome is to find the LCA of two nodes in both a regular binary tree and a Binary Search Tree (BST). This problem tests your ability to navigate tree structures and use recursion effectively to "report" findings from subtrees back to a parent node. Mastering the distinction between the general and BST cases is key to demonstrating deep algorithmic understanding.
Concept 1: Defining the Lowest Common Ancestor
Before writing any code, we need a precise definition. The LCA is a concept that appears frequently, from file systems to version control.
Technical Explanation
The Lowest Common Ancestor (LCA) between two nodes, p and q, in a tree is the lowest (deepest) node that has both p and q as descendants. A crucial part of the definition is that a node is considered a descendant of itself.
This has two important implications:
- If node
pis in the left subtree of a nodeAand nodeqis in the right subtree ofA, thenAis a common ancestor. It might be the lowest one. - If node
pis a direct ancestor of nodeq, thenpitself is the LCA ofpandq.
To get a feel for this, let's watch a short introductory clip.
LOWEST COMMON ANCESTOR OF A BINARY TREE I | PYTHON | LEETCODE 236
The video from the Cracking FAANG channel starts by clearly defining LCA with three distinct examples that cover all the core cases you'll encounter.
Watch from the beginning to 01:46. Pay close attention to the three examples given: nodes in separate subtrees, and one node being an ancestor of the other.
Metaphor: Finding a Common Manager
Think of an organization chart. If you want to find the lowest common manager for two employees, Alice and Bob, you trace their reporting lines upward.
- If Alice is in Marketing and Bob is in Sales, their LCA is the first manager who oversees both departments (e.g., the VP of Growth).
- If Bob is Alice's direct manager, then Bob is their LCA. You don't need to go any higher up the chain.
In a file system, the LCA of /home/user/docs/report.txt and /home/user/photos/vacation.jpg is the /home/user directory.
Concept 2: LCA in a Binary Search Tree (The Efficient Path)
Let's start with the easier version of the problem. In a Binary Search Tree (BST), the ordered nature of the nodes allows for a highly efficient, non-recursive solution.
Technical Explanation
The BST property (left child < parent < right child) gives us a perfect roadmap. Starting from the root, we can determine where p and q lie relative to the current node's value:
- If both
p.valandq.valare less thancurrent.val, we know the LCA must be in the left subtree. So, we move to the left child:current = current.left. - If both
p.valandq.valare greater thancurrent.val, we know the LCA must be in the right subtree. We move to the right child:current = current.right. - If neither of the above is true, it means we've found the split point. This can happen in two ways:
pis in one subtree andqis in the other.- The
currentnode isporq.
In either case, the current node is the LCA.
This approach discards half of the tree at each step, leading to a time complexity of , where is the height of the tree. For a balanced BST, this is . Since we don't use recursion, the space complexity is .

This video provides an excellent walkthrough of this exact logic.
Lowest Common Ancestor of a Binary Search Tree - Leetcode 235 - Python
The video 'Lowest Common Ancestor of a Binary Search Tree' by NeetCode breaks down this iterative approach perfectly.
Watch from 01:23 to 04:44 to understand the logic for the three cases (split, both left, both right), then watch the code walkthrough from 05:10 to 06:32. Notice how the while loop directly implements the decision logic.
Python Code Snippet: Iterative LCA for BST
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def lowestCommonAncestor_BST(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
current = root
while current:
# If both p and q are greater than current, LCA must be in the right subtree.
if p.val > current.val and q.val > current.val:
current = current.right
# If both p and q are less than current, LCA must be in the left subtree.
elif p.val < current.val and q.val < current.val:
current = current.left
# Otherwise, we've found the split point: the LCA.
# This is the case where one is on the left and one is on the right,
# or one of the nodes is the current node.
else:
return current
return None # Should not be reached if p and q are in the tree
Concept 3: LCA in a General Binary Tree (The Recursive Search)
What if the tree isn't a BST? We can no longer use node values to guide our search. We have to search the entire tree. The standard approach is a recursive Depth-First Search (DFS).
Technical Explanation
The core idea is to define a recursive function that returns "evidence" from each subtree. The function findLCA(node, p, q) will return:
- The node itself if it finds
porq. - The LCA if it was found within its subtree.
Noneif it finds neitherpnorqin its subtree.
At any given root, the logic is as follows:
- Base Case: If
rootisNone, or ifrootisporq, returnroot. - Recursive Step: Recursively call
findLCAon the left and right children:left_result = findLCA(root.left, p, q)right_result = findLCA(root.right, p, q)
- Synthesize Results:
- If
left_resultandright_resultare both non-null, it meanspwas found in one subtree andqin the other. Therefore, the currentrootis the LCA. Returnroot. - If only one of them is non-null (e.g.,
left_result), it means either bothpandqare in the left subtree, or just one of them is. In either case, we propagate the non-null result upwards:return left_result(orright_result). - If both are null, neither node was found in this subtree. Return
None.
- If
The time complexity is because we must visit every node in the worst case. The space complexity is due to the recursion stack, which is in the worst case (a skewed tree).
236. Lowest Common Ancestor of a Binary Tree
The article 'Lowest Common Ancestor of a Binary Tree' from AlgoMonster provides a crisp, well-structured explanation of this recursive strategy.
Read the sections 'Intuition' and 'Solution Approach'. Focus on the key insight: 'the first node we encounter that "sees" both p and q in its subtrees ... must be the LCA'. The explanation of the return values is particularly important.
Python Code Snippet: Recursive LCA for Binary Tree
def lowestCommonAncestor_BT(root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
# Base case: if we've reached the end of a branch, or found p or q.
if not root or root == p or root == q:
return root
# Recursively search the left and right subtrees.
left_result = lowestCommonAncestor_BT(root.left, p, q)
right_result = lowestCommonAncestor_BT(root.right, p, q)
# If the recursive calls on both subtrees returned a non-null node,
# it means p and q were found in different subtrees of the current 'root'.
# Therefore, the current 'root' is their LCA.
if left_result and right_result:
return root
# If only one of the subtrees returned a node, it means either:
# 1. Both p and q are in that subtree.
# 2. Only one of p or q has been found so far.
# In either case, we propagate that result upwards.
# If both were null, this correctly returns null.
return left_result or right_result
Concept 4: Under the Hood: Tracing the Recursive Calls
The recursive solution for a general binary tree is elegant but can be tricky to visualize. Let's trace the call stack to solidify your understanding.
Technical Explanation
The magic of the algorithm lies in how information propagates up the call stack. A node doesn't need to know what its children found, only that they found something.
Consider this execution flow:

Let's break down the logic from the algo.monster article you read: return root if left and right else (left or right).
if left and right: This is the "LCA found" case. The currentrootis the pivot point.else (left or right): This is the "propagate evidence" case.- If
leftis a node andrightisNone, this evaluates toleft. - If
leftisNoneandrightis a node, this evaluates toright. - If both are
None, this evaluates toNone.
This single line elegantly handles three distinct outcomes.
- If
Real-world System Example: The DOM Tree
In web development, the Document Object Model (DOM) is a tree structure representing an HTML page. A common task in a framework like React is to find the smallest container element that encloses two different components that were updated. This is exactly an LCA problem. When React needs to re-render efficiently, it finds the smallest common subtree (rooted at the LCA) that needs to be updated, rather than re-rendering the entire page.
Test your understanding!
In the recursive binary tree solution, what would happen if the problem states p and q are not guaranteed to be in the tree? For example, if we search for the LCA of a node that exists and one that doesn't. What would the current algorithm return?
Show answer
The current algorithm would incorrectly return the node that does exist. This is because the search for the non-existent node would always return None. The calls would propagate the single found node all the way up to the root, and that node would be returned as the final answer. To handle this, a top-tier solution would first involve a pre-processing step: two separate traversals to verify that both p and q exist in the tree. Only then would you run the LCA algorithm.
Concept 5: The Parent Pointer Variant (An Elite Interview Twist)
Top companies like Google and Meta often add twists to standard problems to test adaptability. A common variant for LCA is: "What if each node has a parent pointer?"
Technical Explanation
If you have parent pointers, you can solve this problem without recursion and without starting from the root. It becomes a list intersection problem.
Imagine you have two nodes, p and q. The paths from each node up to the root form two singly-linked lists that are guaranteed to intersect (at the LCA and all its ancestors). The problem is now "find the intersection point of two linked lists."
- Calculate Depths: Find the depths of
pandq. - Align Pointers: Move the pointer of the deeper node up by the difference in depths. Now both pointers are at the same level.
- Traverse Upward: Move both pointers up one step at a time, simultaneously. The first node they meet at is the LCA.
This approach is often more efficient if the nodes are very deep in a large tree, as you don't need to visit the entire tree. The time complexity is and space complexity is .
A more elegant version of this avoids calculating depth explicitly.
Leetcode 1650. Lowest Common Ancestor of a Binary Tree III
The resource 'Leetcode 1650. Lowest Common Ancestor of a Binary Tree III' on HelloInterview discusses this parent pointer approach. It highlights a clever two-pointer technique.
Read the section 'Two-pointer convergence pattern'. This describes a technique where you treat the paths to the root as linked lists. When one pointer reaches the root (null parent), you redirect it to the other node's starting point. This guarantees they will meet at the intersection point (the LCA).
This variant tests a different set of skills: recognizing that a problem can be transformed into another well-known pattern (list intersection) and applying the corresponding optimal algorithm.
Conclusion
Today you've explored the Lowest Common Ancestor problem from multiple angles, a requirement for elite-level interviews. You've seen how a problem's constraints (BST vs. general tree) completely change the optimal solution.
Key Takeaways:
- LCA Definition: The lowest node that has both
pandqas descendants, where a node is a descendant of itself. - BST Approach: An efficient iterative solution is possible by using the BST property to navigate towards the "split point."
- Binary Tree Approach: A recursive DFS is required. The key is propagating "evidence" of found nodes up the call stack. The node that receives evidence from both its left and right children is the LCA.
- Recursive Logic: The expression
return root if left and right else (left or right)is a powerful and concise way to implement the recursive logic. - Parent Pointer Variant: If
parentpointers exist, the problem transforms into finding the intersection of two linked lists, solvable in time and space.
Preview of the Next Lesson
Having covered several core tree structures (BT, BST, Heaps), our next lesson introduces a specialized tree optimized for string and prefix-based operations: the Trie (or prefix tree). You will learn to implement a Trie and apply it to solve classic problems like autocomplete and dictionary searches. This is a crucial data structure for many applications dealing with textual data.