title: Kth Smallest Element in a BST
slug: kth-smallest-element-in-a-bst
difficulty: medium
leetcode_id: 230
leetcode_url: https://leetcode.com/problems/kth-smallest-element-in-a-bst/
categories:
- trees
- recursion
patterns:
- dfs
- tree-traversal
function_signature: "def kth_smallest(root: TreeNode | None, k: int) -> int:"
test_cases:
visible:
- input: { root: [3, 1, 4, null, 2], k: 1 }
expected: 1
- input: { root: [5, 3, 6, 2, 4, null, null, 1], k: 3 }
expected: 3
- input: { root: [1], k: 1 }
expected: 1
hidden:
- input: { root: [2, 1, 3], k: 2 }
expected: 2
- input: { root: [5, 3, 6, 2, 4, null, null, 1], k: 6 }
expected: 6
- input: { root: [3, 1, 4, null, 2], k: 4 }
expected: 4
- input: { root: [5, 3, 6, 2, 4, null, null, 1], k: 1 }
expected: 1
- input: { root: [5, 3, 6, 2, 4, null, null, 1], k: 4 }
expected: 4
- input: { root: [41, 37, 44, 24, 39, 42, 48, 1, 35, 38, 40, null, 43, 46, 49, 0, 2, 30, 36, null, null, null, null, null, null, 45, 47, null, null, null, null, null, 4, 29, 32, null, null, null, null, null, null, 3, 9, 26, null, 31, 34, null, null, 7, 11, 25, 27, null, null, 33, null, 6, 8, 10, 16, null, null, null, 28, null, null, 5, null, null, null, null, null, 15, 19, null, null, null, null, 12, null, 18, 20, null, 13, 17, null, null, 22, null, 14, null, null, 21, 23], k: 25 }
expected: 24
description: |
Given the `root` of a binary search tree, and an integer `k`, return the `k`th smallest value (**1-indexed**) of all the values of the nodes in the tree.
constraints: |
- The number of nodes in the tree is `n`
- `1 <= k <= n <= 10^4`
- `0 <= Node.val <= 10^4`
examples:
- input: "root = [3,1,4,null,2], k = 1"
output: "1"
explanation: "The inorder traversal of the tree is [1, 2, 3, 4]. The 1st smallest element is 1."
- input: "root = [5,3,6,2,4,null,null,1], k = 3"
output: "3"
explanation: "The inorder traversal is [1, 2, 3, 4, 5, 6]. The 3rd smallest element is 3."
explanation:
intuition: |
The key insight comes from the fundamental property of a **Binary Search Tree (BST)**: for any node, all values in its left subtree are smaller, and all values in its right subtree are larger.
What does this mean for traversal? If you visit nodes in **inorder** order (left → current → right), you get all values in **sorted ascending order**!
Imagine walking through the tree: you go as far left as possible first (smallest values), then visit the current node, then explore the right subtree. This naturally produces a sorted sequence.
So finding the kth smallest becomes simple: perform an inorder traversal and return the kth element you encounter. No need to collect all values first — you can count as you go and stop early once you've found it.
Think of it like this: the BST is already "pre-sorted" by its structure. The inorder traversal simply reads this sorted order.
approach: |
We solve this using **Inorder Traversal with Early Termination**:
**Step 1: Understand the traversal pattern**
- Inorder traversal visits: left subtree → current node → right subtree
- For a BST, this visits nodes in ascending sorted order
- We count each node we visit until we reach the kth one
**Step 2: Set up tracking variables**
- `count`: Track how many nodes we've visited (starts at `0`)
- `result`: Store the kth smallest value once found
**Step 3: Perform inorder traversal**
- Recursively traverse the left subtree
- Increment `count` when visiting the current node
- If `count == k`, we've found our answer — store it and stop
- Otherwise, recursively traverse the right subtree
**Step 4: Early termination**
- Once we've found the kth element, there's no need to continue traversing
- We can use a flag or check if result is set to stop recursion early
This approach leverages the BST property to avoid sorting, achieving O(H + k) time where H is the tree height.
common_pitfalls:
- title: Collecting All Values Then Sorting
description: |
A naive approach collects all node values into a list, sorts it, and returns the kth element.
This works but wastes both time and space. With n nodes, you'd use O(n) space for the list and O(n log n) time for sorting.
Since BST's inorder traversal is already sorted, we can get O(H + k) time and O(H) space instead.
wrong_approach: "Collect all values, sort, return kth"
correct_approach: "Use inorder traversal property — already sorted"
- title: Not Using Early Termination
description: |
Even with inorder traversal, visiting all n nodes is wasteful when k is small. If k = 1, why traverse the entire tree?
Always check if you've found the kth element and stop early. This improves average case performance significantly, especially when k << n.
wrong_approach: "Complete full inorder traversal"
correct_approach: "Stop as soon as kth element is found"
- title: Off-by-One Errors with 1-Indexed k
description: |
The problem states k is **1-indexed**, meaning k = 1 refers to the smallest element, not the second smallest.
Be careful with your counter: if you start counting at 0, the kth element is found when `count == k`, not `count == k - 1`.
Always clarify the indexing in your head before implementing.
wrong_approach: "Returning element at index k-1 after 0-indexed counting"
correct_approach: "Increment count first, then check if count equals k"
key_takeaways:
- "**BST inorder = sorted order**: This fundamental property is the key to many BST problems"
- "**Early termination**: Stop traversing once you have the answer — don't process unnecessary nodes"
- "**Iterative vs recursive**: Both work; iterative uses an explicit stack and can be easier to control for early termination"
- "**Follow-up insight**: For frequent queries with modifications, augment nodes with subtree sizes for O(H) queries"
time_complexity: "O(H + k). We descend H levels to reach the leftmost node, then visit k nodes. For a balanced tree, this is O(log n + k)."
space_complexity: "O(H). The recursion stack or explicit stack holds at most H nodes, where H is the tree height — O(log n) for balanced, O(n) for skewed."
solutions:
- approach_name: Inorder Traversal (Recursive)
is_optimal: true
code: |
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def kth_smallest(root: TreeNode | None, k: int) -> int:
count = 0
result = 0
def inorder(node: TreeNode | None) -> bool:
nonlocal count, result
if not node:
return False
# Traverse left subtree first (smaller values)
if inorder(node.left):
return True # Already found, stop early
# Visit current node
count += 1
if count == k:
result = node.val
return True # Found the kth smallest
# Traverse right subtree (larger values)
return inorder(node.right)
inorder(root)
return result
explanation: |
**Time Complexity:** O(H + k) — Descend to leftmost node (H steps), then visit k nodes.
**Space Complexity:** O(H) — Recursion stack depth equals tree height.
We perform inorder traversal, counting nodes as we visit them. The `True` return value signals that we've found the answer and should stop recursing. This early termination avoids unnecessary traversal when k is small.
- approach_name: Inorder Traversal (Iterative with Stack)
is_optimal: true
code: |
def kth_smallest(root: TreeNode | None, k: int) -> int:
stack = []
current = root
count = 0
while stack or current:
# Go as far left as possible
while current:
stack.append(current)
current = current.left
# Process the leftmost unvisited node
current = stack.pop()
count += 1
# Check if this is the kth smallest
if count == k:
return current.val
# Move to the right subtree
current = current.right
return -1 # Should never reach here if k is valid
explanation: |
**Time Complexity:** O(H + k) — Same as recursive approach.
**Space Complexity:** O(H) — Explicit stack replaces recursion stack.
The iterative approach uses an explicit stack to simulate recursion. We push nodes while going left, pop to visit, then move right. This gives more control over termination and avoids potential stack overflow for very deep trees.
- approach_name: Collect and Sort (Suboptimal)
is_optimal: false
code: |
def kth_smallest(root: TreeNode | None, k: int) -> int:
values = []
def collect(node: TreeNode | None) -> None:
if not node:
return
# Collect all values via any traversal
values.append(node.val)
collect(node.left)
collect(node.right)
collect(root)
values.sort()
return values[k - 1] # k is 1-indexed
explanation: |
**Time Complexity:** O(n log n) — Collecting is O(n), sorting is O(n log n).
**Space Complexity:** O(n) — Store all n values.
This approach ignores the BST property entirely. It collects all values, sorts them, and returns the kth element. While correct, it's inefficient and doesn't leverage the fact that inorder traversal of a BST is already sorted. Included to illustrate why understanding data structure properties matters.