Given the root of a binary tree, return the lowest common ancestor (LCA) of two given nodes, p and q, in the tree. If either node p or q does not exist in the tree, return null. All values of the nodes in the tree are unique.
According to the definition of LCA on binary tree:
p and q in a binary tree is the lowest node that has both p and q 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 = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 10 Output: null Explanation: Node 10 does not exist in the tree, so return null.
Constraints:
[1, 104].-109 <= Node.val <= 109Node.val are unique.p != qFollow up: Can you find the LCA traversing the tree, without checking nodes?
When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
The brute force way to find the lowest common ancestor of two people in a family tree is to check every possibility. We can do this by looking at all the ancestors of each person. The first ancestor they share in common as we go up the tree is the lowest common ancestor.
Here's how the algorithm would work step-by-step:
def lowest_common_ancestor_brute_force(root, node_one, node_two):
def find_ancestors(node):
ancestors = []
current_node = node
while current_node:
ancestors.append(current_node)
# Assuming each node has a 'parent' attribute.
if not hasattr(current_node, 'parent'):
return []
current_node = current_node.parent
return ancestors
node_one_ancestors = find_ancestors(node_one)
node_two_ancestors = find_ancestors(node_two)
# Reverse the lists to start from the immediate parents.
node_one_ancestors.reverse()
node_two_ancestors.reverse()
# Iterate to find the lowest common ancestor
lowest_common_ancestor = None
for i in range(min(len(node_one_ancestors), len(node_two_ancestors))):
# Stop when there's a divergence in ancestors.
if node_one_ancestors[i] == node_two_ancestors[i]:
lowest_common_ancestor = node_one_ancestors[i]
else:
break
# Return the found common ancestor.
return lowest_common_ancestorThe most efficient way to find the lowest common ancestor (LCA) in a binary tree when we know two nodes exist involves a single tree traversal. The idea is to recursively search for the nodes and track when they are found, returning relevant information up the tree. If both nodes are found in a subtree, the root of that subtree is the LCA.
Here's how the algorithm would work step-by-step:
def lowest_common_ancestor(root, first_node, second_node):
def recurse_tree(node):
if not node:
return None
# Recursively search the left subtree
left_result = recurse_tree(node.left)
# Recursively search the right subtree
right_result = recurse_tree(node.right)
# If the current node is one of the target nodes
node_is_target = node == first_node or node == second_node
# If the target nodes were found in left and right subtrees
if left_result and right_result:
return node
# If the current node is a target node and either target was found in a subtree
if node_is_target and (left_result or right_result):
return node
# If the current node is a target node
if node_is_target:
return node
# Propagate results up the tree.
return left_result or right_result
return recurse_tree(root)| Case | How to Handle |
|---|---|
| Root is null | Return null immediately since there is no tree to traverse. |
| p or q is null | Return null if either p or q are null as LCA cannot be defined. |
| p and q are the same node | Return p (or q) since the lowest common ancestor of a node with itself is the node itself. |
| Either p or q, or both, do not exist in the tree. | Return null if either p or q are not in the tree, which can be determined during the recursive traversal. |
| Large, unbalanced tree (e.g., skewed tree) | Recursive solution may lead to stack overflow; consider iterative solution using a parent pointer map. |
| Tree with a single node that is either p or q. | If the root is equal to either p or q, check if the other node is present in the tree; if not, return null, otherwise return the root. |
| p is an ancestor of q or q is an ancestor of p | The algorithm should correctly identify the ancestor as the LCA in these situations. |
| All node values are the same, potentially including p and q. | The algorithm relies on object identity (pointer comparison), not value comparison, so identical values should not affect correctness. |