Mastering LeetCode: Solving the Lowest Common Ancestor of a Binary Tree Problem
Solving the Lowest Common Ancestor of a Binary Tree on LeetCode
Introduction
Finding the Lowest Common Ancestor (LCA) of two nodes in a binary tree is a classic problem often encountered in coding interviews and algorithm studies. Understanding how to solve this problem not only improves your problem-solving skills but also deepens your understanding of binary trees and recursive algorithms.
In this article, we will explore the problem, discuss a recursive approach to solving it, and implement the solution in Python.
Understanding the Problem
Given a binary tree, find the lowest common ancestor (LCA) of two given nodes in the tree.
According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes
p
andq
as the lowest node inT
that has bothp
andq
as descendants (where we allow a node to be a descendant of itself).”Example 1:
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 Output: 3 Explanation: The LCA of nodes 5 and 1 is 3.
Example 2:
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4 Output: 5 Explanation: The LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.
Example 3:
Input: root = [1,2], p = 1, q = 2 Output: 1
Constraints:
The number of nodes in the tree is in the range
[2, 10^5]
.
-10^9 <= Node.val <= 10^9
All
Node.val
are unique.
p != q
p
andq
will exist in the tree.
Recursive Depth-First Search Approach
To solve the problem, we can use a recursive depth-first search (DFS) approach. Here's the step-by-step strategy:
Base Case
The base case of the recursion checks if the current node is None
. If it is, we return None
, indicating that we have reached the end of a branch without finding p
or q
.
Match Case
If the current node is either p
or q
, we return the current node. This indicates that we have found one of the nodes we are looking for.
Recursive Search
We recursively search the left and right subtrees of the current node. If both left and right recursive calls return non-None
values, it means p
and q
are found in different subtrees, making the current node the LCA. If only one of the recursive calls returns a non-None
value, it means both p
and q
are located in the same subtree, and we return the non-None
result.
Implementing the Solution in Python
Here’s the Python implementation of the recursive DFS approach:
from typing import Optional
# Definition for a binary tree node.
class TreeNode:
def __init__(self, val: int = 0, left: Optional['TreeNode'] = None, right: Optional['TreeNode'] = None):
self.val = val
self.left = left
self.right = right
class Solution:
def lowestCommonAncestor(self, root: Optional[TreeNode], p: TreeNode, q: TreeNode) -> Optional[TreeNode]:
# Base case: if root is None, there's no LCA
if not root:
return None
# If the current node is either p or q, return it
if root == p or root == q:
return root
# Recursively search the left and right subtrees
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
# If both left and right are non-None, current node is the LCA
if left and right:
return root
# Otherwise, return non-None value from left or right
return left if left else right
Explanation of the Code
Base Case: The check
if not root
ensures we returnNone
if the current node isNone
, indicating the end of a branch.Match Case: The check
if root == p or root == q
returns the current node if it matches eitherp
orq
.Recursive Search: The recursive calls
left = self.lowestCommonAncestor(root.left, p, q)
andright = self.lowestCommonAncestor(root.right, p, q)
search forp
andq
in the left and right subtrees.Determine LCA: The check
if left and right
returns the current node as the LCA if both recursive calls are non-None
. Otherwise, it returns the non-None
result from the left or right subtree.
Edge Cases and Considerations
If
p
andq
are the same node, the algorithm correctly returns that node as the LCA.The solution assumes that both
p
andq
exist in the tree, as specified in the problem constraints.
FAQ Section
What is the time complexity of the solution?
The time complexity of the solution is O(N)
, where N
is the number of nodes in the tree. This is because, in the worst case, the algorithm may need to traverse all the nodes in the tree.
Can this solution be adapted for a non-binary tree?
Yes, the solution can be adapted for a non-binary tree. However, the recursive search would need to be modified to iterate over all children of a node instead of just left and right children.
What are some common mistakes to avoid?
Not handling the case where one of the nodes is
None
.Assuming that the nodes
p
andq
are always different and exist in the tree without checking for it.
Conclusion
Understanding and implementing the Lowest Common Ancestor problem in a binary tree is a valuable exercise in recursive problem-solving and binary tree traversal. By using a recursive depth-first search approach, we can efficiently find the LCA of two nodes, ensuring our solution is both correct and optimal.
Practice similar problems to further strengthen your grasp of these concepts.
Related Articles
"Mastering LeetCode's "Lowest Common Ancestor in a BST": A Comprehensive Guide"
"Mastering the Backspace String Compare on LeetCode: A Comprehensive Guide"
"Mastering LeetCode: Counting the Number of Islands in a 2D Binary Grid"
"Getting Started with Studying for Software Engineering Interviews Using LeetCode"
Subscribe to my newsletter
Read articles from Sean Coughlin directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Sean Coughlin
Sean Coughlin
Software Engineer