176. Kth Smallest in BST
Problem
Given a valid strict BST and one-based rank k, return its kth smallest value. Raise ValueError if k<=0 or the tree contains fewer than k nodes. Do not mutate the tree. The algorithm may stop before visiting the entire tree.
Examples
Example 1
kth_smallest_bst(build_tree([3,1,4,None,2]),3)
=> 3The third inorder emission is the root value.
Example 2
kth_smallest_bst(build_tree([2,1,3]),1)
=> 1The first emission is the leftmost node.
Example 3
kth_smallest_bst(build_tree([7]),1)
=> 7A singleton has one valid rank.
Reveal the worked solution and reasoning
Why this mechanism works
Inorder of a BST is increasing, so ranking becomes counting emission events. An explicit stack performs the left descent, then emits the next smallest node and moves to its right subtree. The stack contains unfinished ancestors rather than all values in sorted order.
State and boundaries
stack holds ancestors waiting for their inorder emission. k is the number of remaining emissions needed to reach the requested rank.
Protocol
- Reject nonpositive k.
- Push the entire current left spine.
- Pop and emit the next node, decrementing k.
- Return its value when k becomes zero.
- Continue from its right child.
- Reject if traversal ends first.
Visualization
BST values in inorder: 1 -> 2 -> 3 -> 4
k starts 3; remaining: 2,1,0
return at the third pop, without collecting all valuesPython implementation
from structures import build_tree
def kth_smallest_bst(root, k):
if k <= 0:
raise ValueError('k must be positive')
stack, node = [], root
while node is not None or stack:
while node is not None:
stack.append(node)
node = node.left
node = stack.pop()
k -= 1
if k == 0:
return node.val
node = node.right
raise ValueError('rank exceeds tree size')
Worked execution
kth_smallest_bst(build_tree([3,1,4,None,2]),3)
L4 kth_smallest_bst: if k <= 0: root=N1(val=3,left=N2,right=N3); k=3 L6 kth_smallest_bst: stack, node = [], root root=N1(val=3,left=N2,right=N3); k=3; stack=[]; node=N1(val=3,left=N2,right=N3) L9 kth_smallest_bst: stack.append(node) root=N1(val=3,left=N2,right=N3); k=3; stack=[N1(val=3,left=N2,right=N3)]; node=N1(val=3,left=N2,right=N3) L10 kth_smallest_bst: node = node.left root=N1(val=3,left=N2,right=N3); k=3; stack=[N1(val=3,left=N2,right=N3)]; node=N2(val=1,left=None,right=N4) L9 kth_smallest_bst: stack.append(node) root=N1(val=3,left=N2,right=N3); k=3; stack=[N1(val=3,left=N2,right=N3), N2(val=1,left=None,right=N4)]; node=N2(val=1,left=None,right=N4) L10 kth_smallest_bst: node = node.left root=N1(val=3,left=N2,right=N3); k=3; stack=[N1(val=3,left=N2,right=N3), N2(val=1,left=None,right=N4)]; node=None L11 kth_smallest_bst: node = stack.pop() root=N1(val=3,left=N2,right=N3); k=3; stack=[N1(val=3,left=N2,right=N3)]; node=N2(val=1,left=None,right=N4) L12 kth_smallest_bst: k -= 1 root=N1(val=3,left=N2,right=N3); k=2; stack=[N1(val=3,left=N2,right=N3)]; node=N2(val=1,left=None,right=N4) ... 4 intermediate state changes omitted ... L12 kth_smallest_bst: k -= 1 root=N1(val=3,left=N2,right=N3); k=1; stack=[N1(val=3,left=N2,right=N3)]; node=N4(val=2,left=None,right=None) L15 kth_smallest_bst: node = node.right root=N1(val=3,left=N2,right=N3); k=1; stack=[N1(val=3,left=N2,right=N3)]; node=None L11 kth_smallest_bst: node = stack.pop() root=N1(val=3,left=N2,right=N3); k=1; stack=[]; node=N1(val=3,left=N2,right=N3) L12 kth_smallest_bst: k -= 1 root=N1(val=3,left=N2,right=N3); k=0; stack=[]; node=N1(val=3,left=N2,right=N3)
Result: 3. The third inorder emission is the root value.
kth_smallest_bst(build_tree([2,1,3]),1)
L4 kth_smallest_bst: if k <= 0: root=N1(val=2,left=N2,right=N3); k=1 L6 kth_smallest_bst: stack, node = [], root root=N1(val=2,left=N2,right=N3); k=1; stack=[]; node=N1(val=2,left=N2,right=N3) L9 kth_smallest_bst: stack.append(node) root=N1(val=2,left=N2,right=N3); k=1; stack=[N1(val=2,left=N2,right=N3)]; node=N1(val=2,left=N2,right=N3) L10 kth_smallest_bst: node = node.left root=N1(val=2,left=N2,right=N3); k=1; stack=[N1(val=2,left=N2,right=N3)]; node=N2(val=1,left=None,right=None) L9 kth_smallest_bst: stack.append(node) root=N1(val=2,left=N2,right=N3); k=1; stack=[N1(val=2,left=N2,right=N3), N2(val=1,left=None,right=None)]; node=N2(val=1,left=None,right=None) L10 kth_smallest_bst: node = node.left root=N1(val=2,left=N2,right=N3); k=1; stack=[N1(val=2,left=N2,right=N3), N2(val=1,left=None,right=None)]; node=None L11 kth_smallest_bst: node = stack.pop() root=N1(val=2,left=N2,right=N3); k=1; stack=[N1(val=2,left=N2,right=N3)]; node=N2(val=1,left=None,right=None) L12 kth_smallest_bst: k -= 1 root=N1(val=2,left=N2,right=N3); k=0; stack=[N1(val=2,left=N2,right=N3)]; node=N2(val=1,left=None,right=None)
Result: 1. The first emission is the leftmost node.
Why the solution is correct
Every pop is the next inorder node because all of its left subtree is complete and its right subtree has not begun. BST ordering makes these emissions globally increasing. The kth pop therefore has the requested rank; exhaustion proves that rank does not exist.
Time and space
O(h+k) time for a valid rank, bounded by O(n), and O(h) auxiliary stack space. Invalid large k requires O(n) traversal.
Common mistakes
Using zero-based counting with a one-based contract returns the wrong rank. A BST can be skewed, so O(log n) height is not guaranteed.
Reasoning card
State: stack holds ancestors waiting for their inorder emission. k is the number of remaining emissions needed to reach the requested rank.
Invariant: Visited inorder nodes are the smallest values in order.
Decision to rehearse: Exploit sorted inorder traversal and stop at count k.
Finish: Reject if traversal ends first
Transfer exercise
Augment nodes with subtree sizes to answer repeated rank queries without traversing k nodes.