Start learning free

LESSON 346 OF 442 · BINARY TREES

Kth Smallest Element in a BST in TypeScript: Iterative In-Order, O(h + k)

Practice O(n) Read the full explanation

Given the root of a binary search tree (BST) and an integer k, return the k-th smallest value among all the values in the tree. k counts from 1: k = 1 asks for the smallest value.

In a BST, every value in a node's left subtree is smaller than the node's value, and every value in its right subtree is larger.

The trees below are written in level order (top to bottom, left to right), with null for a missing child.

 

Example 1:

    3
   / \
  1   4
   \
    2

Input: root = [3,1,4,null,2], k = 1
Output: 1
Explanation: In increasing order the values are 1, 2, 3, 4. The 1st smallest is 1.

Example 2:

        5
       / \
      3   6
     / \
    2   4
   /
  1

Input: root = [5,3,6,2,4,null,null,1], k = 3
Output: 3
Explanation: In increasing order the values are 1, 2, 3, 4, 5, 6. The 3rd smallest is 3.

Example 3:

Input: root = [5,3,6,2,4,null,null,1], k = 6
Output: 6
Explanation: The tree has 6 nodes, so the 6th smallest value is the largest one.

 

Constraints:

  • The number of nodes in the tree, n, is in the range [1, 1000].
  • Each node value is between -104 and 104, and all values are different.
  • 1 <= k <= n

 

Follow up: Can you stop as soon as you reach the answer, in O(h + k) time, where h is the height of the tree?
STEP 1 OF 12
    Ctrl + Enter to check

    TYPESCRIPT · BINARY TREES

    Kth Smallest Element In Bst in TypeScript, explained

    Problem overview

    The method kthSmallest receives the root of a binary search tree and a number k, counted from 1, and returns the k-th smallest value in the tree. All values are different, and k is between 1 and the number of nodes.

    For the tree [3, 1, 4, null, 2] the values in increasing order are 1, 2, 3, 4, so k = 1 gives 1. For [5, 3, 6, 2, 4, null, null, 1] the order is 1, 2, 3, 4, 5, 6, so k = 3 gives 3 and k = 6 gives 6.

    Approach: in-order traversal, stopped early

    In a binary search tree, an in-order traversal (the left subtree, then the node, then the right subtree) visits the values in increasing order. So the k-th node the traversal visits holds the answer, and the traversal can stop right there instead of visiting the rest.

    The recursive version is short, but stopping in the middle of a recursion needs extra bookkeeping. Here the traversal uses an explicit stack instead:

    • From the current node, go left as far as possible, pushing every node on the way. These nodes wait: they are bigger than everything in their left subtree.
    • Pop the top node. It is the smallest value not visited yet. Count it.
    • If the count equals k, return its value. Otherwise continue with its right subtree, whose values come next in order.

    On the second example with k = 3: starting at 5, the walk pushes 5, 3, 2 and 1, then reaches null. It pops 1 (count 1) and its right side is empty, so the next pop is 2 (count 2). It has no right child either, so the next pop is 3 (count 3), which equals k. The answer is 3, and 4 and 6 were never visited.

    Step by step

    1. Create stack as a const, typed TreeNode[]. It holds nodes that were passed on the way down and not visited yet.
    2. Create node with let and set it to root. It is the root of the part of the tree still to walk.
    3. Create count with let, starting at 0, the number of visited nodes.
    4. Loop while node || stack.length > 0: there is a subtree to walk or a node waiting on the stack. Only when both are exhausted is the traversal over.
    5. Go left with an inner loop that runs while node is not null.
    6. Push node inside it. It waits until its left subtree has been visited.
    7. Move to node.left.
    8. Pop the top node into node, writing ! after pop(). This is the smallest unvisited value.
    9. Count the visit with count++.
    10. Return node.val if count === k.
    11. Otherwise move to node.right. Its values are larger than this node but smaller than the nodes still on the stack.
    12. Return -1 after the loop. Since k is at most the number of nodes, the loop returns first, and the line only keeps the return type honest.

    The solution in TypeScript

    class Solution {
        kthSmallest(root: TreeNode | null, k: number): number {
            const stack: TreeNode[] = [];
            let node = root;
            let count = 0;
            while (node || stack.length > 0) {
                while (node) {
                    stack.push(node);
                    node = node.left;
                }
                node = stack.pop()!;
                count++;
                if (count === k) {
                    return node.val;
                }
                node = node.right;
            }
            return -1;
        }
    }
    

    The TypeScript details worth a look:

    • The ! after pop(). Array.prototype.pop returns undefined for an empty array, so with strict null checking its type is TreeNode | undefined. The non-null assertion ! tells the compiler that the stack isn't empty here. It is erased in the JavaScript output and checks nothing at run time. It is true here because the inner loop has just pushed a node, or the outer condition guaranteed one on the stack.
    • Truthiness as a null check. while (node) and node || stack.length > 0 rely on objects being truthy and null falsy. That is safe for nodes. It would not be for numbers, where 0 is falsy.
    • One variable, two jobs. node is first the subtree to walk, then the node just popped, then its right child. Its inferred type, TreeNode | null, covers all three.
    • A const stack still changes. const only stops the variable from being reassigned. push and pop modify the array in place.

    Complexity

    Let h be the height of the tree.

    Time: O(h + k). The first descent pushes at most h nodes. After that, the loop pops one node per visit, and each pop is paired with the pushes of its left spine, so the total work to reach the k-th node is bounded by h + k. Even a full traversal pushes and pops each node at most once, so it is O(n).

    Space: O(h). The stack holds the nodes along one path from the root. For a tree shaped like a list that is up to n nodes.

    Edge cases and common mistakes

    • A single node. [7] with k = 1 pushes 7, pops it, counts 1 and returns 7.
    • k equal to the number of nodes. For the six-node tree, k = 6 visits everything and returns 6, the largest value.
    • A node with only a right child. In [3, 1, 4, null, 2] the node 1 has only the child 2. After popping 1, the walk goes to node.right and finds 2 before returning to 3. With k = 4 the answer is 4.
    • Counting from 0. k = 1 means the smallest value, so the check is count === k after incrementing.
    • Using a pre-order walk. Visiting the node before its left subtree gives the values in tree order, not in sorted order.
    • Forgetting the right subtree. Without node = node.right, the loop would keep popping the same stack and miss every value that is in a right subtree.

    Testing

    • [3, 1, 4, null, 2] with k = 1 gives 1.
    • [3, 1, 4, null, 2] with k = 4 gives 4.
    • [5, 3, 6, 2, 4, null, null, 1] with k = 3 gives 3.
    • [5, 3, 6, 2, 4, null, null, 1] with k = 6 gives 6.
    • [7] with k = 1 gives 7.
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free