Start learning free

LESSON 346 OF 442 · BINARY TREES

Kth Smallest Element in a BST in Scala: Iterative In-Order Traversal

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

    SCALA · BINARY TREES

    Kth Smallest Element In Bst in Scala, explained

    Problem overview

    The method kthSmallest receives the root of a binary search tree and an integer k, and returns the k-th smallest value in the tree. Counting starts at 1, so k = 1 asks for the minimum. The values are all different, and k is between 1 and the number of nodes.

    For the tree [3, 1, 4, null, 2], with root 3, children 1 and 4, and 2 as the right child of 1, the values in increasing order are 1, 2, 3, 4. With k = 1 the answer is 1. For the tree [5, 3, 6, 2, 4, null, null, 1] the values in order are 1, 2, 3, 4, 5, 6, so k = 3 gives 3 and k = 6 gives 6.

    Approach: visit the nodes in increasing order and stop at the k-th

    In a binary search tree, everything in a node's left subtree is smaller than the node, and everything in its right subtree is larger. An in-order traversal (the left subtree, then the node, then the right subtree) therefore visits the values from smallest to largest. The k-th node it visits holds the answer.

    A recursive traversal is short, but it is awkward to stop early from deep inside recursion. The lesson uses an explicit stack instead, which makes the traversal a plain loop that can return the moment the count reaches k. This matches the follow-up: O(h + k) time, where h is the height of the tree. The traversal descends to the leftmost node, which takes up to h steps, and then visits k nodes in total.

    The stack holds the nodes on the way down that still wait for their turn. The loop repeats three actions:

    1. From the current node, go left as far as possible, pushing every node on the way. The leftmost node holds the smallest value of this part of the tree.
    2. Pop the top node. Everything smaller than it has been visited. Count it. If the count is k, it is the answer.
    3. Otherwise move to its right child. That subtree holds the next larger values, bigger than the popped node but smaller than the nodes still waiting on the stack. The loop then repeats.

    Trace the first example with k = 1. Push 3, go left, push 1, and go left again to null. Pop 1: the count becomes 1, which equals k, so the answer is 1. The nodes 4 and 2 are never looked at.

    Step by step

    1. Create the stack in a val: an empty scala.collection.mutable.Stack[TreeNode](). push puts a node on top, and pop takes the top one off.
    2. Declare node with var, starting at root. It is the root of the part of the tree that remains to be walked.
    3. Declare count with var, starting at 0. It is the number of nodes visited so far.
    4. Write the outer loop that runs while node != null || stack.nonEmpty. There is work left if there is a subtree to walk or a node waiting on the stack.
    5. Write an inner loop that runs while node != null, to go left.
    6. Push node inside it. It waits there until its left subtree has been visited.
    7. Move to node.left.
    8. Pop the top after the inner loop with node = stack.pop(). It is the smallest value not yet visited.
    9. Count the visit with count += 1.
    10. Return when count == k: return node.value. The rest of the tree is never touched.
    11. Otherwise go right with node = node.right, so the next pass walks that subtree.
    12. Return -1 after the loop. Since k is at most the number of nodes, this line is never reached. The method still has to produce an Int on every path.

    The solution in Scala

    class Solution {
        def kthSmallest(root: TreeNode, k: Int): Int = {
            val stack = scala.collection.mutable.Stack[TreeNode]()
            var node = root
            var count = 0
            while (node != null || stack.nonEmpty) {
                while (node != null) {
                    stack.push(node)
                    node = node.left
                }
                node = stack.pop()
                count += 1
                if (count == k) {
                    return node.value
                }
                node = node.right
            }
            return -1
        }
    }
    

    The Scala details worth knowing:

    • mutable.Stack. The collection is in scala.collection.mutable. push adds on top, pop() removes and returns the top element, and nonEmpty tells whether anything is left. Calling pop() on an empty stack throws NoSuchElementException. In this loop the outer condition guarantees that the stack is non-empty when pop() runs, because the inner loop either pushed something or the stack was non-empty to begin with.
    • The type argument. Stack[TreeNode]() creates an empty stack of TreeNodes. Without the type, the compiler would have nothing to infer it from.
    • var node and val stack. The stack object is changed in place and the variable never points elsewhere, so val is right. node is reassigned on every step, so it is a var.
    • || in the loop condition. It means "keep going while there is a subtree to walk or a node waiting". With && the loop would stop too early, because at the start the stack is empty.
    • return inside nested loops. It leaves the whole method at once, not only the loop it is in. That is what the early stop needs.
    • Counting from 1. count is incremented before the comparison, so the first visited node, the smallest, makes it 1 and answers k = 1.

    Complexity

    Time: O(h + k). The first descent costs up to h pushes, and after that each visited node costs a pop plus the pushes of its right subtree's left spine. Every node is pushed once and popped at most once, and the loop stops after k pops. Without the early return, the whole traversal would be O(n).

    Space: O(h). The stack holds at most one root-to-node path, which is the height of the tree. For a balanced tree that is about log n. For a tree that is a single left-leaning chain it is n.

    Edge cases and common mistakes

    • A single node. The only valid k is 1. The node is pushed, popped, counted, and returned.
    • k equals the number of nodes. The answer is the largest value, found after a full traversal. For the second example tree with k = 6, the method returns 6.
    • A node with only a right child. In [3, 1, 4, null, 2], node 1 has a right child 2 and no left one. After 1 is popped and counted, node = node.right sends the walk into 2 before 3 is popped. The order 1, 2, 3, 4 comes out right.
    • Counting when pushing. A node pushed on the way down is not yet visited, because its left subtree comes first. Count when popping.
    • Forgetting node = node.right. After the pop, node still points at the popped node, so the inner loop would push it again and the walk would never move on. The right subtrees would never be seen.
    • Treating k as a 0-based index. k = 1 is the smallest value, not the second.
    • Sorting all values. Copying the values into a list and sorting works, but costs O(n log n), and ignores the order the tree already has.

    Testing

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