Start learning free

LESSON 346 OF 442 · BINARY TREES

Kth Smallest Element in a BST in Kotlin: Iterative In-Order Walk

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

    KOTLIN · BINARY TREES

    Kth Smallest Element In Bst in Kotlin, explained

    Problem overview

    The function kthSmallest receives the root of a binary search tree and an integer k, counted from 1, and returns the k-th smallest value in the tree. In a BST, every value in a node's left subtree is smaller than the node and every value in its right subtree is larger. All values are different, and k is between 1 and the number of nodes.

    For the tree with root 3, left child 1 (which has a right child 2) and right child 4, the values in increasing order are 1, 2, 3, 4. With k = 1 the answer is 1. For the tree with root 5, children 3 and 6, then 2 and 4 under 3 and 1 under 2, the sorted values are 1 to 6, so k = 3 gives 3 and k = 6 gives 6.

    Approach: in-order traversal, stopped early

    An in-order traversal visits the left subtree, then the node, then the right subtree. On a BST this visits the values in increasing order. So the k-th node the traversal visits holds the k-th smallest value, and the traversal can stop right there.

    A recursive traversal is the textbook version, but stopping in the middle of recursion needs extra bookkeeping. The lesson uses a stack instead, which makes the order explicit. The stack holds the nodes passed on the way down that are still waiting to be visited. The idea in three moves:

    1. From the current node, go left as far as possible, pushing every node on the way. The last node reached has the smallest value in that part of the tree.
    2. Pop the top of the stack. Everything smaller than it has already been visited. Visit it: count it, and return its value if the count equals k.
    3. Otherwise, continue from its right child, whose values come next in the order.

    For the second example tree and k = 3: the walk pushes 5, 3, 2 and 1, and pops 1 (count 1). Node 1 has no right child, so the next pop is 2 (count 2), then 3 (count 3), which is the answer.

    Step by step

    1. Create stack with val as an empty ArrayDeque<TreeNode>(). addLast and removeLast both work at the end, so it acts as a stack.
    2. Declare node with var and set it to root. It is the root of the part of the tree still to walk. Its type is TreeNode?.
    3. Declare count with var, starting at 0: the number of nodes visited so far.
    4. Write the outer loop that runs while node != null or stack.isNotEmpty(). The traversal is over only when there is no part of the tree left to walk and no node waiting on the stack.
    5. Write an inner loop that runs while node != null. It heads to the leftmost node, because each step left reaches smaller values.
    6. Push node with addLast. It waits on the stack until its left subtree has been visited.
    7. Move node to node.left.
    8. After the inner loop, pop the top with removeLast() and store it in node. This is the smallest value not yet visited. After this line, Kotlin knows node is not null.
    9. Visit it: count++.
    10. If count == k, return node.value. The rest of the tree is never visited.
    11. Otherwise move node to node.right. The next pass walks that subtree, whose values are larger than this node but smaller than the nodes still on the stack.
    12. Return -1 after the outer loop. It never runs, since k is at most the number of nodes, but a function that returns an Int has to return one on every path.

    The solution in Kotlin

    class Solution {
        fun kthSmallest(root: TreeNode?, k: Int): Int {
            val stack = ArrayDeque<TreeNode>()
            var node = root
            var count = 0
            while (node != null || stack.isNotEmpty()) {
                while (node != null) {
                    stack.addLast(node)
                    node = node.left
                }
                node = stack.removeLast()
                count++
                if (count == k) {
                    return node.value
                }
                node = node.right
            }
            return -1
        }
    }
    

    The Kotlin details worth a closer look:

    • A stack of non-null nodes. ArrayDeque<TreeNode> can't hold null. addLast(node) compiles inside the inner loop because node is smart-cast to TreeNode there.
    • Assigning from removeLast(). node has the type TreeNode?, and removeLast() returns a TreeNode. After the assignment, the compiler knows node is not null until it is assigned again, so node.value and node.right are allowed.
    • removeLast() on an empty deque. It throws an exception. It is safe here: the outer loop runs only if node != null or the stack is not empty, and after the inner loop node is null, so the stack must hold something.
    • count++. It is used as a statement on its own line, and it increments the var.
    • Values and k. The tree's field is called value, so there is no keyword clash, unlike a field named val.

    Complexity

    Let h be the height of the tree.

    Time: O(h + k). Reaching the first node takes h pushes. After that, every visit pops one node, and every node is pushed once, so reaching the k-th visit takes at most h + k pushes and k pops. In the worst case, k is the number of nodes, and the time is O(n).

    Space: O(h). The stack never holds more than one root-to-leaf path. A balanced tree has h about log n, and a tree shaped like a list has h equal to n.

    Edge cases and common mistakes

    • k = 1. The answer is the leftmost node. In the first example tree, the walk pushes 3 and 1, then pops 1 and returns it.
    • k equals the number of nodes. The answer is the largest value. For the second example tree and k = 6 it is 6.
    • A single node. The only valid k is 1, and the first pop returns the root's value.
    • An off-by-one count. k counts from 1, and count is incremented before it is compared with k, so the first visited node has count == 1. Comparing before the increment would return the node after the right one.
    • Visiting before going left. That gives a preorder walk, whose order is not sorted, so the count no longer matches the ranks.
    • Forgetting the right child. Without node = node.right, the walk would never visit the values larger than a popped node unless they were already on the stack.
    • Stopping the outer loop on node != null alone. After the first pop, node can be null because the popped node had no right child, while the stack still holds the nodes whose turn comes next. The loop must also run while the stack is not empty.

    Testing

    • Root 3, left 1 (right child 2), right 4, with k = 1 gives 1; k = 2 gives 2; k = 4 gives 4.
    • Root 5, children 3 and 6, then 2 and 4 under 3, and 1 under 2, with k = 3 gives 3; k = 5 gives 5; k = 6 gives 6.
    Keep going with KotlinAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free