THN Interview Prep

235. Lowest Common Ancestor of a Binary Search Tree

At a Glance

  • Topic: trees
  • Pattern: BST property (search-space shrink from ordered keys)
  • Difficulty: Medium
  • Companies: Amazon, Google, Meta, Microsoft, Bloomberg
  • Frequency: Very High
  • LeetCode: 235

Problem (one-liner)

Given a BST’s root and two distinct nodes primary and secondary that exist in the tree, return their lowest common ancestor—the deepest node that has both nodes as descendants (a node can be a descendant of itself).

Recognition Cues

  • BST + LCA together → walk from root using comparisons to p and q
  • No parent pointers needed
  • Values are comparable at each step

Diagram

At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.

Loading diagram…

Approaches

  • Brute force — store paths root→p and root→q as arrays — O(h) time / O(h) space — then scan last common element.
  • Better — recursive BST split — O(h) time / O(h) stack — same idea as iterative but with frames.
  • OptimalO(h) time / O(1) space — walk: if both targets smaller than current, go left; both larger, go right; otherwise current splits them or equals one → LCA.

Optimal Solution

Go

package main

type TreeNode struct {
	Val   int
	Left  *TreeNode
	Right *TreeNode
}

func lowestCommonAncestor(root *TreeNode, primary *TreeNode, secondary *TreeNode) *TreeNode {
	small := primary
	large := secondary
	if primary.Val > secondary.Val {
		small = secondary
		large = primary
	}
	current := root
	for current != nil {
		if current.Val < small.Val {
			current = current.Right
			continue
		}
		if current.Val > large.Val {
			current = current.Left
			continue
		}
		return current
	}
	return nil
}

JavaScript

class TreeNode {
	constructor(val = 0, left = null, right = null) {
		this.val = val;
		this.left = left;
		this.right = right;
	}
}

function lowestCommonAncestor(root, primary, secondary) {
	let small = primary.val <= secondary.val ? primary : secondary;
	let large = primary.val <= secondary.val ? secondary : primary;
	let current = root;
	while (current !== null) {
		if (current.val < small.val) {
			current = current.right;
		} else if (current.val > large.val) {
			current = current.left;
		} else {
			return current;
		}
	}
	return null;
}

Walkthrough

BST: 6 / 2,8 / 0,4,7,9 — find LCA of 2 and 8.

currentaction
62 < 6 < 8 → split range → return 6

For 2 and 4: start 6 → both < 6 → go left 2 → now 2 is one endpoint and 4 > 2 → from 2 go right to 4’s region… Actually LCA of 2 and 4: walk from 6: both less → 2. At 2: one node is 2 (current), other is 4>2 → still must descend right to 4? Rule: return first node where p and q fall on opposite sides or equal current. At 2, small=2, large=4: current.val(2) >= small and <= large → return 2. Good.

Invariant: BST invariant guarantees first node inside [min(p,q), max(p,q)] is the LCA.

Edge Cases

  • One node is ancestor of the other → answer is the ancestor (handled by inclusive range)
  • All nodes along one spine — still O(h)

Pitfalls

  • Using generic binary-tree LCA on BST and missing O(h) constant-space advantage
  • Swapping p/q without normalizing min/max (optional but clearer)

Similar Problems

Variants

  • Parent pointers only → different walk up levels.
  • Smallest / largest value in common subtree between two nodes.

Mind-Map Tags

#trees #bst #lca #range-split #iterative-walk

Last updated on

Spotted something unclear or wrong on this page?

On this page