Courses › Coding Interviews › Patterns
Binary Search Trees
Validation with bounds, inorder = sorted, LCA, deletion and balanced builds.
A binary search tree (BST) adds one invariant to a binary tree: every value in a node's left subtree is smaller than the node, and every value in its right subtree is larger. That invariant gives you two powers. You can discard half of the tree at each step, and an inorder traversal visits the values in sorted order.
Search, insert, and the O(h) cost
def search(node, target):
while node and node.val != target:
node = node.left if target < node.val else node.right
return node
def insert(node, val):
if node is None:
return TreeNode(val)
if val < node.val:
node.left = insert(node.left, val)
else:
node.right = insert(node.right, val)
return nodeBoth run in O(h). For a balanced BST that's O(log n), but inserting sorted data produces a linked list where h = n. Mention this. Production code uses self-balancing trees (red-black, AVL) or B-trees, and that's what sorted containers in Java (TreeMap) and C++ (std::map) are built on.
Validating a BST: pass bounds down
The classic trap is checking only left.val < node.val < right.val. That misses a deep node that violates an ancestor's bound.
def is_valid_bst(node, lo=float("-inf"), hi=float("inf")):
if node is None:
return True
if not (lo < node.val < hi):
return False
return (is_valid_bst(node.left, lo, node.val) and
is_valid_bst(node.right, node.val, hi))An equally good alternative: an inorder traversal must be strictly increasing.
Inorder = sorted
Many BST questions are really "iterate the values in sorted order":
- Kth smallest: inorder traversal, stop at the kth node.
- Minimum absolute difference: compare each value with the previous one in inorder.
- Two Sum in a BST: inorder into a sorted list, then use two pointers.
- Recover a BST with two swapped nodes: find the inversions in the inorder sequence.
def kth_smallest(root, k):
stack, node = [], root
while True:
while node:
stack.append(node)
node = node.left
node = stack.pop()
k -= 1
if k == 0:
return node.val
node = node.rightLCA is easier in a BST
You don't need to search both subtrees. Walk down from the root. If both values are smaller, go left. If both are larger, go right. Otherwise the current node is where they split.
def lca_bst(node, p, q):
while node:
if p.val < node.val and q.val < node.val:
node = node.left
elif p.val > node.val and q.val > node.val:
node = node.right
else:
return nodeDeletion
Deletion is the operation candidates fumble most. There are three cases:
- Leaf: remove it.
- One child: replace the node with its child.
- Two children: copy the inorder successor (the smallest value in the right subtree) into the node, then delete that successor from the right subtree.
def delete(node, key):
if node is None:
return None
if key < node.val:
node.left = delete(node.left, key)
elif key > node.val:
node.right = delete(node.right, key)
else:
if node.left is None:
return node.right
if node.right is None:
return node.left
succ = node.right
while succ.left:
succ = succ.left
node.val = succ.val
node.right = delete(node.right, succ.val)
return nodeBuilding a balanced BST
From a sorted array, pick the middle element as the root and recurse on each half. The result has height O(log n). The same idea balances an existing BST: do an inorder traversal into a list, then rebuild.
[!INTERVIEW] Clarify duplicates Ask whether the tree can contain duplicate values and which side they go on. It changes the validation condition (< vs <=) and several follow-ups.
[!TIP] Practice Validate BST · Kth Smallest · LCA of a BST · Delete Node · Sorted Array to BST · Inorder Successor · Recover BST