Forgetting null/base-case handling
Wrong move: Recursive traversal assumes children always exist.
Usually fails on: Leaf nodes throw errors or create wrong depth/path values.
Fix: Handle null/base cases before recursive transitions.
Move from brute-force thinking to an efficient approach using tree strategy.
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.Problem summary: 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).”
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Tree
[6,2,8,0,4,7,9,null,null,3,5] 2 8
[6,2,8,0,4,7,9,null,null,3,5] 2 4
[2,1] 2 1
lowest-common-ancestor-of-a-binary-tree)smallest-common-region)lowest-common-ancestor-of-a-binary-tree-ii)lowest-common-ancestor-of-a-binary-tree-iii)lowest-common-ancestor-of-a-binary-tree-iv)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #235: Lowest Common Ancestor of a Binary Search Tree
/**
* 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) {
while (true) {
if (root.val < Math.min(p.val, q.val)) {
root = root.right;
} else if (root.val > Math.max(p.val, q.val)) {
root = root.left;
} else {
return root;
}
}
}
}
// Accepted solution for LeetCode #235: Lowest Common Ancestor of a Binary Search Tree
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
for {
if root.Val < min(p.Val, q.Val) {
root = root.Right
} else if root.Val > max(p.Val, q.Val) {
root = root.Left
} else {
return root
}
}
}
# Accepted solution for LeetCode #235: Lowest Common Ancestor of a Binary Search Tree
# 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: 'TreeNode', p: 'TreeNode', q: 'TreeNode'
) -> 'TreeNode':
while 1:
if root.val < min(p.val, q.val):
root = root.right
elif root.val > max(p.val, q.val):
root = root.left
else:
return root
// Accepted solution for LeetCode #235: Lowest Common Ancestor of a Binary Search Tree
use std::rc::Rc;
use std::cell::RefCell;
impl Solution {
pub fn lowest_common_ancestor(
root: Option<Rc<RefCell<TreeNode>>>,
p: Option<Rc<RefCell<TreeNode>>>,
q: Option<Rc<RefCell<TreeNode>>>,
) -> Option<Rc<RefCell<TreeNode>>> {
let (p, q) = (p.unwrap().borrow().val, q.unwrap().borrow().val);
let mut root = root;
while let Some(node) = root {
if p > node.borrow().val && q > node.borrow().val {
root = node.borrow().right.clone();
} else if p < node.borrow().val && q < node.borrow().val {
root = node.borrow().left.clone();
} else {
return Some(node.clone());
}
}
unreachable!()
}
}
// Accepted solution for LeetCode #235: Lowest Common Ancestor of a Binary Search Tree
/**
* Definition for a binary tree node.
* class TreeNode {
* val: number
* left: TreeNode | null
* right: TreeNode | null
* constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* }
*/
function lowestCommonAncestor(
root: TreeNode | null,
p: TreeNode | null,
q: TreeNode | null,
): TreeNode | null {
while (root) {
if (root.val > p.val && root.val > q.val) {
root = root.left;
} else if (root.val < p.val && root.val < q.val) {
root = root.right;
} else {
return root;
}
}
}
Use this to step through a reusable interview workflow for this problem.
BFS with a queue visits every node exactly once — O(n) time. The queue may hold an entire level of the tree, which for a complete binary tree is up to n/2 nodes = O(n) space. This is optimal in time but costly in space for wide trees.
Every node is visited exactly once, giving O(n) time. Space depends on tree shape: O(h) for recursive DFS (stack depth = height h), or O(w) for BFS (queue width = widest level). For balanced trees h = log n; for skewed trees h = n.
Review these before coding to avoid predictable interview regressions.
Wrong move: Recursive traversal assumes children always exist.
Usually fails on: Leaf nodes throw errors or create wrong depth/path values.
Fix: Handle null/base cases before recursive transitions.