Skip to content

Coding patternsBlind 75

Kth Smallest Element in a BST

An in-order walk of a BST visits the values in sorted order, so the k-th node it visits is the answer. Solved two ways, a full in-order list in O(n) and an in-order walk with a stack that stops at k in O(h + k), with a step-through, code in five languages and the Big O worked out from the code.

You are given the root of a binary search tree (BST) and a number rank. In a BST, every value in the left subtree of a node is smaller than the node, and every value in the right subtree is larger.

Return the rank-th smallest value in the tree. rank counts from 1, so rank = 1 asks for the smallest value.

Trees in the examples are written level by level, from left to right. null marks a missing child.

Example 1

Input: root = [7, 3, 10, 1, 5, null, 12, null, 2], rank = 3

Output: 3

Sorted, the values are 1, 2, 3, 5, 7, 10, 12. The 3rd one is 3.

731251012

Example 2

Input: root = [4, 2, 6], rank = 3

Output: 6

Sorted: 2, 4, 6. The 3rd one is the largest, 6.

Example 3

Input: root = [1], rank = 1

Output: 1

The only value is also the smallest.

  • The tree has n nodes. Call rank k, with 1 ≤ k ≤ n ≤ 10,000.
  • 0 ≤ Node.val ≤ 10,000
Does rank count from 0 or from 1?
From 1. rank = 1 means the smallest value.
Is rank always valid?
Yes. 1 ≤ rank ≤ n, so the answer always exists.
Can values repeat?
No. In a BST here, left is strictly smaller and right strictly larger.
Is the tree balanced?
Not always. It can be a chain, so its height h can be as large as n.

The tree is a BST, and “k-th smallest” is about sorted order. An in-order traversal (left subtree, then the node, then the right subtree) of a BST visits the values in sorted order. So the k-th node it visits is the answer, and the walk can stop right there.

This problem has two approaches. There is no useful step between them.

Optimal O(h + k)

Do the same in-order walk, but by hand with a stack, a list where the last item in is the first out. Then the walk can stop the moment it reaches the k-th value.

The smallest value left is always at the far left. So walk left from the current node and push every node on the way. Pop the top: nothing smaller is still waiting, so it is the next value in order. Count it. If it is the k-th, return it. If not, the next values are in its right subtree, so go right and walk left again from there.

Why it never misses the answer: the stack produces exactly the in-order sequence, which is the sorted order. Counting pops counts sorted values, so the k-th pop is the k-th smallest.

731251012
stack top →
empty
rank
3
  1. stack, node = [], root
  2. while True:
  3. while node:
  4. stack.append(node)
  5. node = node.left
  6. node = stack.pop()
  7. rank -= 1
  8. if rank == 0:
  9. return node.val
  10. node = node.right

The smallest value is at the far left. Walk there, and push each node on the way onto a stack, so we can come back up in order. Dim nodes are not reached yet, and stk marks nodes on the stack.

1 / 11
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// In-order walk with a stack, stopped at the k-th node. It only goes
// down one path and then k nodes forward, so about h + k steps.
public int kthSmallest(TreeNode root, int rank) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode node = root;
while (true) {
// The smallest value left is at the far left. Remember the
// path there, because each node on it comes next in order.
while (node != null) {
stack.push(node);
node = node.left;
}
node = stack.pop();
// This is the next value in sorted order.
if (--rank == 0) return node.val;
// Values just above this one live in its right subtree.
node = node.right;
}
}
}
Time O(h + k)Space O(h)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The work is in the lit lines: each push walks one step left, and each pop takes one value. Both are a fixed job, so each push and each pop counts as 1 step. We count them.

    1. stack, node = [], root
    2. while True:
    3. while node:
    4. stack.append(node)
    5. node = node.left
    6. node = stack.pop()
    7. rank -= 1
    8. if rank == 0:
    9. return node.val
    10. node = node.right
  2. 2 Count the runs on Example 1

    The table shows every step on Example 1. The walk pushes 7, 3 and 1 on its way down. Then it pops 1, pushes 2, pops 2, and pops 3, which is the 3rd pop.

    4 pushes + 3 pops = 7 steps.

    step1234567
    actionpush 7push 3push 1pop 1push 2pop 2pop 3
    k after3332210
  3. 3 Write the count for any n

    The walk stops at the k-th pop, so there are exactly k pops. A node is pushed only once, and every pushed node is either popped or still on the stack at the end. The stack holds at most one path, so at most h nodes. So:

    pops = k, pushes ≤ k + h, total ≤ 2k + h

    Check it with Example 1: k = 3 and h = 4, so the bound is 10. The real run used 7.

  4. 4 Keep only the part that grows fastest

    Drop the 2. Doubling k does not change how the count grows with k. What is left is h + k. When k is small and the tree is balanced, that is far less than n. In the worst case, k = n and the walk visits every node, so it is never slower than the brute force.

    So the time is O(h + k).

  5. S Space: count the extra memory the same way

    The stack holds the nodes on one path from the root down, so at most h of them. The code adds only node. So the space is O(h): about log n for a balanced tree, and n for a chain.

    stack (up to h nodes)node

2,000 random search trees of 1 to 10 values, with every possible rank. The brute force and the optimal solution gave the same answer every time.

Largest inputsResult
10,000 nodes in one long line to the left (the most allowed), with rank 10,000, so the walk passes every node. Optimal.0.001 s, and about 0.08 MB of extra memory: the stack holds all 10,000 nodes at once, which is the O(h) space.
10,000 nodes, balanced, with rank 10,000. Optimal.0.001 s, and under 0.01 MB of extra memory: the stack holds at most about 14 nodes.