LeetCode 235: Lowest Common Ancestor of a Binary Search Tree Solution in Python – A Step-by-Step Guide

Picture finding the shared ancestor of two nodes in a binary search tree, like tracing a family tree back to a common grandparent—that’s the essence of LeetCode 235: Lowest Common Ancestor of a Binary Search Tree! This easy-level problem challenges you to identify the lowest common ancestor (LCA) of two nodes in a BST, leveraging its ordered structure. Using Python, we’ll explore two solutions: the Best Solution (a BST property-based traversal) for its efficiency and elegance, and an Alternative Solution (a path comparison approach) for its clarity. With beginner-friendly breakdowns, detailed examples, and clear code, this guide will help you master LCA in BSTs and boost your coding skills. Let’s find that ancestor!

What Is LeetCode 235: Lowest Common Ancestor of a Binary Search Tree?

Section link icon

In LeetCode 235: Lowest Common Ancestor of a Binary Search Tree, you’re given the root of a BST and two nodes p and q, and your task is to return their lowest common ancestor—the deepest node that is an ancestor of both. A BST’s property (left < root < right) makes this simpler than in a general binary tree. This differs from linked list problems like LeetCode 234: Palindrome Linked List, focusing on tree navigation.

Problem Statement

  • Input: Root of a BST and two nodes p and q.
  • Output: The LCA node of p and q.
  • Rules: Use BST properties; p and q exist in the tree.

Constraints

  • Number of nodes: 2 to 10^5.
  • Node values: -10^9 to 10^9, unique.
  • p ≠ q, and both are in the tree.

Examples

  • Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
    • Tree:
```
   6
  / \
 2   8
/ \ / \

0 4 7 9 / \ 3 5 ```

  • Output: 6 (LCA of 2 and 8).
  • Input: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
    • Output: 2 (LCA of 2 and 4, as 2 is an ancestor of 4).

    Understanding the Problem: Finding the LCA

    Section link icon

    To solve LeetCode 235: Lowest Common Ancestor of a Binary Search Tree in Python, we need to locate the node where the paths to p and q diverge. Unlike digit-counting tasks like LeetCode 233: Number of Digit One, this is about BST traversal. In a BST, if both nodes are less than the root, the LCA is in the left subtree; if both are greater, it’s in the right; if one is less and one is greater, the root is the LCA. We’ll explore two approaches: 1. Best Solution (BST Property-Based Traversal): O(h) time, O(1) space—leverages BST order. 2. Alternative Solution (Path Comparison): O(h) time, O(h) space—stores paths explicitly.

    Let’s start with the best solution.

    Best Solution: BST Property-Based Traversal

    Section link icon

    Why This Is the Best Solution

    The BST property-based traversal is our top choice for LeetCode 235 because it uses the BST’s ordered nature to find the LCA in a single pass with no extra space beyond pointers. It’s efficient, intuitive for BSTs, and meets the problem’s constraints perfectly.

    How It Works

    • Start at the root.
    • Compare p.val and q.val with the current node’s value:
      • If both are less, move left.
      • If both are greater, move right.
      • If one is less and one is greater (or equal), the current node is the LCA.
    • Continue until the split point is found.

    Step-by-Step Example

    Example 1: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8

    • Tree Description:
      • Root: 6
      • Left: 2 (left: 0, right: 4; 4’s left: 3, right: 5)
      • Right: 8 (left: 7, right: 9)
    • Process:
      • Node 6: p=2 < 6, q=8 > 6 → Split here, LCA = 6.
    • Output: 6.

    Example 2: p = 2, q = 4

    • Process:
      • Node 6: 2 < 6, 4 < 6 → Left to 2.
      • Node 2: 2 = 2, 4 > 2 → Split at 2 (2 is ancestor of 4).
    • Output: 2.

    Code with Detailed Line-by-Line Explanation

    Here’s the Python implementation:

    # 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':
            # Line 1: Start at root
            curr = root
    
            # Line 2: Traverse based on BST properties
            while curr:
                # Line 3: Both p and q less than current
                if p.val < curr.val and q.val < curr.val:
                    curr = curr.left
                # Line 4: Both p and q greater than current
                elif p.val > curr.val and q.val > curr.val:
                    curr = curr.right
                # Line 5: Split point found (one less, one greater, or equal)
                else:
                    return curr
    
            # Line 6: Return LCA (won’t reach here due to problem constraints)
            return curr
    
    • Time Complexity: O(h) – height of the tree, worst case O(n) for skewed tree.
    • Space Complexity: O(1) – only uses a pointer.

    Alternative Solution: Path Comparison Approach

    Section link icon

    Why an Alternative Approach?

    The path comparison approach finds the LCA by recording paths from the root to p and q, then identifying the last common node. It’s a great alternative if you prefer explicit path tracking or want a method that’s easier to adapt to general binary trees, though it uses more space.

    How It Works

    • Traverse from root to p and q, storing paths in lists.
    • Compare paths to find the last common node before divergence.

    Step-by-Step Example

    Example: p = 2, q = 8

    • Path to p=2: [6, 2].
    • Path to q=8: [6, 8].
    • Compare: Common nodes = [6], last = 6.
    • Output: 6.

    Example: p = 3, q = 5

    • Path to p=3: [6, 2, 4, 3].
    • Path to q=5: [6, 2, 4, 5].
    • Compare: Common = [6, 2, 4], last = 4.
    • Output: 4.

    Code for Path Comparison Approach

    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':
            # Line 1: Find path to a node
            def find_path(node, target):
                path = []
                curr = node
                while curr:
                    path.append(curr)
                    if target.val < curr.val:
                        curr = curr.left
                    elif target.val > curr.val:
                        curr = curr.right
                    else:
                        break
                return path
    
            # Line 2: Get paths to p and q
            path_p = find_path(root, p)
            path_q = find_path(root, q)
    
            # Line 3: Find last common node
            lca = None
            for i in range(min(len(path_p), len(path_q))):
                if path_p[i] != path_q[i]:
                    break
                lca = path_p[i]
    
            # Line 4: Return LCA
            return lca
    
    • Time Complexity: O(h) – two path traversals and comparison.
    • Space Complexity: O(h) – stores paths.

    Comparing the Two Solutions

    Section link icon
    • Best Solution (BST Property-Based Traversal):
      • Pros: O(1) space, single pass, BST-optimized.
      • Cons: Relies on BST properties.
    • Alternative Solution (Path Comparison):
      • Pros: Clear path logic, adaptable to non-BST.
      • Cons: O(h) space.

    The BST property-based method is our best for its efficiency and minimal space use.

    Additional Examples and Edge Cases

    Section link icon

    Root as LCA

    • Input: p = 0, q = 9
    • Output: 6 (root).

    Same Node

    • Input: p = 4, q = 4
    • Output: 4 (node itself).

    Deep Nodes

    • Input: p = 3, q = 5
    • Output: 4.

    Complexity Breakdown

    Section link icon
    • BST Property-Based:
      • Time: O(h).
      • Space: O(1).
    • Path Comparison:
      • Time: O(h).
      • Space: O(h).

    BST method excels in space efficiency.

    Key Takeaways for Beginners

    Section link icon
    • LCA: Deepest common ancestor.
    • BST Property: Guides traversal direction.
    • Path Comparison: Tracks full routes.
    • Python Tip: Pointers in trees—see [Python Basics](/python/basics).

    Final Thoughts: Find Ancestors Like a Pro

    Section link icon

    LeetCode 235: Lowest Common Ancestor of a Binary Search Tree in Python is a delightful BST challenge. The BST property-based traversal offers a sleek, space-saving solution, while the path comparison approach provides clear insight. Want more tree adventures? Try LeetCode 236: Lowest Common Ancestor of a Binary Tree or LeetCode 104: Maximum Depth of Binary Tree. Ready to find LCAs? Head to Solve LeetCode 235 on LeetCode and trace those ancestors today!