Given a binary search tree (BST), find the lowest common ancestor (LCA) node of two given nodes in the BST.
According to the definition of LCA on Wikipedia: “The lowest common ancestor is defined between two nodes p and q as the lowest node in T that has both p and q as descendants (where we allow a node to be a descendant of itself).”
Example 1:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 Output: 6 Explanation: The LCA of nodes 2 and 8 is 6.
Example 2:
Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4 Output: 2 Explanation: The LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.
Example 3:
Input: root = [2,1], p = 2, q = 1 Output: 2
Constraints:
[2, 105].-109 <= Node.val <= 109Node.val are unique.p != qp and q will exist in the BST.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 in a binary search tree involves exploring all possible ancestor candidates. We essentially check every node in the tree to see if it is an ancestor of both of our target nodes. It’s like trying every single suspect in a mystery until you find the culprit.
Here's how the algorithm would work step-by-step:
def lowest_common_ancestor_brute_force(root, first_node, second_node):
lowest_common_ancestor = None
def is_ancestor(potential_ancestor, target_node):
if potential_ancestor is None:
return False
if potential_ancestor == target_node:
return True
return is_ancestor(potential_ancestor.left, target_node) or \
is_ancestor(potential_ancestor.right, target_node)
def check_node_is_lca(node):
nonlocal lowest_common_ancestor
# See if current node is ancestor of both nodes.
if is_ancestor(node, first_node) and is_ancestor(node, second_node):
# Update lowest ancestor if current is lower
if lowest_common_ancestor is None:
lowest_common_ancestor = node
elif get_depth(node) > get_depth(lowest_common_ancestor):
lowest_common_ancestor = node
def get_depth(node):
if node is None:
return 0
depth = 0
current = root
#Traverse down to the node, incrementing depth
while(current != node):
if(node.val < current.val):
current = current.left
else:
current = current.right
depth += 1
return depth
def traverse(node):
if node is not None:
check_node_is_lca(node)
traverse(node.left)
traverse(node.right)
#Iterate every node, looking for suitable ancestor
traverse(root)
return lowest_common_ancestorThe goal is to find the shared ancestor that is closest to two specific nodes in a binary search tree. Instead of searching randomly, we will use the properties of the binary search tree to efficiently navigate toward the answer.
Here's how the algorithm would work step-by-step:
def lowest_common_ancestor(root_node, node_p, node_q):
current_node = root_node
while current_node:
# If both nodes are smaller, LCA is in the left subtree.
if node_p.val < current_node.val and node_q.val < current_node.val:
current_node = current_node.left
# If both nodes are larger, LCA is in the right subtree.
elif node_p.val > current_node.val and node_q.val > current_node.val:
current_node = current_node.right
# Current node is the LCA.
else:
return current_node| Case | How to Handle |
|---|---|
| Root is null | Return null if the root is null, as there is no tree. |
| p or q is null | Throw an IllegalArgumentException because the search nodes should exist. |
| p and q are the same node | Return p (or q) as it is the lowest common ancestor of itself. |
| p or q is not in the tree | The problem statement typically assumes that p and q are in the tree, so handle it by returning null or throwing an exception, depending on requirements. |
| p is an ancestor of q | The algorithm should correctly identify p as the LCA. |
| q is an ancestor of p | The algorithm should correctly identify q as the LCA. |
| Large BST with deeply nested nodes | The iterative approach scales efficiently as it avoids recursion overhead, preventing stack overflow errors. |
| BST with skewed distribution (e.g., all nodes on one side) | The algorithm should still correctly find the LCA, potentially with slightly higher time complexity depending on the path to p and q, but not impacting correctness. |