Lowest Common Ancestor (LCA) in a Binary Tree
The Lowest Common Ancestor (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).
Problem Statementβ
Given a binary tree and two nodes p
and q
, find their lowest common ancestor.
Node Class Representationβ
C++ Class Definition:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
Python Class Definition:
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
Solution Approach: Recursive Strategyβ
We use a recursive approach to solve the LCA problem efficiently. The idea is to traverse the tree and return the node if it matches either p
or q
. Otherwise, we check the left and right subtrees for p
and q
and determine the LCA based on the results.
C++ Implementationβ
#include <iostream>
using namespace std;
// Definition for a binary tree node
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root || root == p || root == q) return root; // Base case
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if (left && right) return root; // If both left and right are not null, root is the LCA
return left ? left : right; // Otherwise, return the non-null child
}
};
Python Implementationβ
class Solution:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root == p or root == q: # Base case
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root # If both left and right are not null, root is the LCA
return left if left else right # Otherwise, return the non-null child
Time Complexity: O(N)
β
N
is the number of nodes in the binary tree.- We traverse each node of the binary tree only once.
Space Complexity: O(H)
β
H
is the height of the tree, which represents the space used by the recursion stack.- In the worst case (a skewed tree),
H
can beO(N)
. In a balanced tree,H
isO(log N)
.
Key Concepts to Understandβ
- Binary Tree Traversal: Understanding how to traverse the binary tree recursively.
- Recursive Approach: Using recursion to check for
p
andq
in both left and right subtrees. - Base Cases: Properly handling cases where the current node is
p
orq
, or if the current node isnullptr
.
Solution Approach: Iterative Strategy (Optional)β
For cases where recursion is not ideal, an iterative approach using parent pointers and a set can be used. Hereβs a brief outline for this approach:
- Use a hash map to store parent pointers of all nodes in the tree.
- Use this information to track ancestors of
p
and then find the first common ancestor ofq
.
Python Implementation (Iterative)β
def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
parent = {root: None}
stack = [root]
# Traverse the tree until both nodes are found
while p not in parent or q not in parent:
node = stack.pop()
if node.left:
parent[node.left] = node
stack.append(node.left)
if node.right:
parent[node.right] = node
stack.append(node.right)
# Store ancestors of p in a set
ancestors = set()
while p:
ancestors.add(p)
p = parent[p]
# Find the first common ancestor of q
while q not in ancestors:
q = parent[q]
return q
Time Complexity: O(N)
β
- Traversing the tree to fill the parent pointers takes
O(N)
time. - Tracing back the ancestors of
p
andq
also takesO(N)
time.
Space Complexity: O(N)
β
- The hash map and ancestor set both use
O(N)
space.
Summaryβ
- The LCA Problem is a fundamental question in tree data structures, often appearing in coding interviews.
- The recursive solution is simple and efficient for balanced binary trees.
- The iterative solution is useful when recursion depth might become a concern.
Mastering this problem will strengthen your understanding of binary tree traversal and recursion, making it easier to solve more complex tree-based problems. Happy coding!