# Definition for a binary tree node.
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def lowestCommonAncestor(self, root, p, q):
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return left if left else right
// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
class Solution {
public:
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;
}
};
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
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;
}
}
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @param {TreeNode} p
* @param {TreeNode} q
* @return {TreeNode}
*/
var lowestCommonAncestor = function(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;
};
You are given the root node root
of a binary tree and two distinct nodes p
and q
within that tree. Your task is to find the lowest common ancestor (LCA) of nodes p
and q
.
The lowest common ancestor of two nodes p
and q
is defined as the deepest node in the tree that has both p
and q
as descendants (where we allow a node to be a descendant of itself).
p
and q
are guaranteed to exist in the tree.p
and q
.
At first glance, the problem asks us to identify the lowest node in a binary tree that is an ancestor to both p
and q
. A brute-force approach might be to find the paths from the root to p
and q
, then compare the paths to find the last common node.
However, this approach requires extra space to store the paths and extra time to compare them. Is there a way to do this more efficiently, perhaps in a single traversal?
If we think recursively, we can ask: at any node, does its left or right subtree contain p
or q
? If both subtrees return a non-null result, then the current node must be the LCA. This insight leads us toward a divide-and-conquer strategy, which is both elegant and efficient.
null
, return null
(base case).p
or q
, return the node itself (since a node is a descendant of itself).p
and q
.p
and q
are found in different subtrees, so the current node is their LCA.null
, return null
.This approach is efficient because it only visits each node once, and does not require storing extra paths or parent pointers.
Consider the following binary tree:
3 / \ 5 1 / \ / \ 6 2 0 8 / \ 7 4
Suppose p = 5
and q = 1
. Let's trace the steps:
3
. It's not p
or q
.5
. This matches p
, so return 5
up.1
. This matches q
, so return 1
up.3
, both left and right returned non-null (5
and 1
), so 3
is their LCA.
If p = 5
and q = 4
, the recursion would find both in the left subtree, and thus the LCA would be 5
itself.
O(N)
to find each path, so O(N)
for both paths (where N
is the number of nodes), plus O(N)
to compare them.O(N)
for storing each path.O(N)
, since we visit each node at most once.O(H)
, where H
is the height of the tree, due to the recursion stack. For a balanced tree, H = log N
; for a skewed tree, H = N
.
The lowest common ancestor problem in a binary tree can be solved efficiently using a simple recursive approach. By leveraging the properties of recursion and the definition of ancestry, we avoid unnecessary storage or repeated traversals. The key insight is that if p
and q
are found in different subtrees of a node, that node is their LCA. This makes the solution both elegant and optimal in terms of time and space.