Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 33

Kth Smallest/Largest in BST

Recommended resources

Hello! Welcome to your next lesson on Binary Search Trees.

In our last session, we focused on validating BSTs. The key insight was that an in-order traversal of a valid BST always produces elements in a sorted, ascending order. This property is not just for validation; it's a powerful tool for solving other problems, which is exactly what we'll explore today.

Our goal is to find the k-th smallest or largest element in a BST. This is a classic interview question that directly builds on your understanding of BST properties and traversal algorithms. Mastering it will equip you with an elegant pattern applicable to a range of tree-based problems.

The Core Idea: Traversal is Sorting

The problem asks for the k-th smallest element. Given what we know from the last lesson, if we perform an in-order traversal (Left -> Node -> Right), we are essentially "visiting" the nodes in their sorted order. Therefore, the k-th smallest element is simply the k-th node we visit during an in-order traversal.

A naive approach would be to perform a full in-order traversal, store all the node values in a list, and then return the element at index k-1.

# Naive approach
def kthSmallest_naive(root, k):
    in_order_list = []
    
    def traverse(node):
        if not node:
            return
        traverse(node.left)
        in_order_list.append(node.val)
        traverse(node.right)
    
    traverse(root)
    return in_order_list[k-1]

This works, but it has a time complexity of and, more importantly, a space complexity of to store the list. In an interview, you'll be expected to do better, especially on space. The key is to stop the traversal as soon as we find the k-th element.


Approach 1: Recursive In-order Traversal with a Counter

We can adapt the recursive in-order traversal to stop early. The idea is to maintain a counter or decrement k each time we "visit" a node (after processing its left subtree). When the count reaches k (or k reaches 0), we've found our target.

This guide provides a very clear plan and implementation for this recursive strategy.

Kth Smallest Element in a BST

The article 'Kth Smallest Element in a BST' from CodePath outlines the plan for a recursive in-order solution. It's a great example of structured problem-solving for interviews.

Please read the 'M-atch' and 'P-lan' sections to understand why in-order traversal is the right choice and to review the pseudocode for the recursive helper function. Then, look at the Python code in the 'I-mplement' section.

The core logic, as shown in the article, involves a helper function that passes k by reference (or uses nonlocal in Python) and decrements it at the "visit" step of the traversal. This stops unnecessary work and keeps space complexity tied to the recursion depth, , where is the tree's height.


Approach 2: Iterative In-order Traversal (High-ROI Interview Pattern)

While recursion is intuitive, an iterative solution using a stack is a fundamental pattern for tree problems in interviews. It avoids recursion depth limits and demonstrates a deeper command of traversal mechanics. The logic remains the same: simulate an in-order traversal and stop after visiting k elements.

This video from NeetCode provides an excellent step-by-step visualization and implementation of the iterative approach.

Kth Smallest Element in a BST - Leetcode 230 - Python

The video 'Kth Smallest Element in a BST' explains the iterative in-order traversal using a stack. Pay close attention to how the stack is used to simulate the recursion's call stack.

Please watch from 02:12 to 10:54. The first part (until 06:49) walks through the algorithm with a diagram, and the second part implements it in Python. Focus on the loop structure: going all the way left while pushing to the stack, then popping, processing, and moving right.

Here's the Python code, which is a template you should become very familiar with:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        stack = []
        current = root

        while current or stack:
            # 1. Go left and push nodes onto the stack
            while current:
                stack.append(current)
                current = current.left
            
            # 2. Pop the last node (the next smallest)
            current = stack.pop()
            
            # 3. "Visit" the node
            k -= 1
            if k == 0:
                return current.val
            
            # 4. Move to the right subtree
            current = current.right
  • Time Complexity: In the worst case, we might need to visit k nodes, so the complexity is , where is the height of the tree (for the initial descent). If k is close to N, this is .
  • Space Complexity: for the stack.
Test your understanding!

Consider this BST and k = 3:

      5
     / \
    3   7
   / \   \
  2   4   8

Using the iterative approach, what are the contents of the stack right before the node with value 3 is popped?

Show answer
  1. current starts at 5. The outer while loop begins.
  2. The inner while loop executes:
    • Push 5 onto the stack. stack is [5]. current becomes 3.
    • Push 3 onto the stack. stack is [5, 3]. current becomes 2.
    • Push 2 onto the stack. stack is [5, 3, 2]. current becomes None.
  3. The inner loop terminates. We pop 2. k becomes 2. current moves to 2's right child (None).
  4. The outer loop continues. The inner loop is skipped as current is None. We are about to pop the next element, which is 3.
  5. At this moment, the stack contains [5]. The element 3 had already been popped in the previous step, and its right child 4 would be pushed in the next step. Let's re-trace carefully.

Correct Trace:

  1. Start: current=5, stack=[]
  2. Inner loop:
    • stack.append(5), current=3
    • stack.append(3), current=2
    • stack.append(2), current=None
    • Stack is [5, 3, 2]
  3. Pop 2. k=2. current = 2.right (None).
  4. Outer loop, current is None. Pop 3. stack is now [5].
    So, just before 3 is popped, the stack contains [5, 3]. After it's popped, the stack contains [5].

Finding the k-th Largest Element

Now, how can we adapt this to find the k-th largest element?

K-th Smallest and Largest Element in a Binary Search Tree
Caption: A clear illustration of finding the k-th smallest vs. k-th largest element for k=2. The in-order sequence is [2, 4, 6, 8, 9, 10, 12]. The 2nd smallest is 4. The 2nd largest is 10.

There are two excellent ways to solve this.

Method 1: Using the Total Node Count

The k-th largest element is equivalent to the -th smallest element, where N is the total number of nodes in the tree. This approach would involve:

  1. A first pass to count all nodes (N).
  2. A second pass using one of our kthSmallest methods with the new k value of N - k + 1.

This is a valid solution, but it requires two passes over the tree.

Method 2: Reverse In-order Traversal

A more elegant, single-pass solution exists. If a regular in-order traversal (Left -> Node -> Right) gives us elements in ascending order, what would a reverse in-order traversal (Right -> Node -> Left) give us? Descending order!

Therefore, the k-th largest element is simply the k-th node we visit during a reverse in-order traversal.

The following resource explains this approach beautifully.

kth Largest Element in BST

The article 'kth Largest Element in BST' from Scaler Topics explains how to solve this variant efficiently. We'll focus on the recursive reverse in-order traversal.

Please read 'Approach 2: Recursive Approach - using Reverse In-Order Traversal'. Focus on how the traversal order is changed to Right -> Node -> Left and how a counter is used to find the k-th element in this descending sequence.

You can implement this both recursively and iteratively, just by swapping the left and right traversal logic.


Follow-up: Handling Frequent Queries

In a FAANG-level interview, a common follow-up question is: "What if the tree is modified frequently (many insertions/deletions) and you need to answer kthSmallest queries very fast?"

Our current approaches take or time for each query. If we have many queries, this becomes inefficient. The optimal solution involves augmenting the TreeNode structure.

Augmented Binary Search Tree:
We can modify the TreeNode class to include a field, say left_subtree_size, which stores the number of nodes in its left subtree.

class AugmentedTreeNode:
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None
        self.left_subtree_size = 0 # or total subtree size

When we search for the k-th smallest element, we can use these counts to decide whether to go left, right, or stop:

  1. Start at the root. Let the number of nodes in the left subtree be L (current.left_subtree_size).
  2. The rank of the current node is L + 1.
  3. If k == L + 1, the current node is our answer.
  4. If k < L + 1, the element is in the left subtree. Recurse left, looking for the k-th smallest.
  5. If k > L + 1, the element is in the right subtree. Recurse right, but now we're looking for the (k - (L + 1))-th smallest element in that subtree.
Finding the K-th Smallest Element in a BST
Caption: This image visualizes finding the k-th element by using subtree sizes. At each node, the size of the left subtree tells you the rank of the current node, allowing you to narrow the search in O(H) time.

This augmented approach reduces the query time to , the height of the tree. The trade-off is that insertions and deletions become slightly more complex, as you need to update the subtree size counts all the way up to the root, but this is still an operation.


Conclusion

Today we leveraged a core BST property to solve a classic interview problem.

Key Takeaways:

  • The k-th smallest element in a BST is the k-th element in an in-order traversal (Left -> Node -> Right).
  • The k-th largest element is the k-th element in a reverse in-order traversal (Right -> Node -> Left).
  • For interviews, be prepared to implement these traversals both recursively and iteratively (using a stack). The iterative approach is a crucial, high-ROI pattern.
  • For follow-up questions about frequent queries, the optimal solution is an augmented BST that stores subtree sizes, reducing query time from to .

In our next lesson, we will tackle another BST construction problem: constructing a height-balanced BST from a sorted array. You will see how choosing the right element for the root at each step is key, a concept related to what we explored today.