Start learning free

LESSON 347 OF 443 · BINARY TREES

Kth Smallest Element in a BST in Swift: 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 nil for a missing child.

 

Example 1:

    3
   / \
  1   4
   \
    2

Input: root = [3,1,4,nil,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,nil,nil,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,nil,nil,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

    SWIFT · BINARY TREES

    Kth Smallest Element In Bst in Swift, 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. The values are all different, and k is between 1 and the number of nodes.

    For the tree 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, so k = 1 gives 1. For the tree with root 5, children 3 and 6, and below 3 the nodes 2 and 4 (with 1 under 2), the order is 1, 2, 3, 4, 5, 6: k = 3 gives 3, and k = 6 gives 6.

    Approach: in-order traversal with a stack

    An in-order traversal visits the left subtree, then the node, then the right subtree. In a binary search tree, that visits the values in increasing order, so the k-th node visited holds the answer.

    A recursive traversal is simple but runs to the end of the tree. The follow-up asks for O(h + k) time, which means stopping at the k-th node. That is easier with an explicit stack, where you can just return from the middle of the loop.

    The stack holds nodes you walked past on the way down and haven't visited yet. The procedure is:

    • From the current node, go left as far as possible, pushing every node. The smallest value of that part of the tree is at its leftmost node.
    • Pop a node. It is the smallest value not yet visited, because all smaller values, including its entire left subtree, were visited earlier. Count it.
    • If the count equals k, return its value. Otherwise continue with its right subtree.

    On the second example with k = 3: the descent pushes 5, 3, 2 and 1. Pop 1: count 1, no right child. Pop 2: count 2, no right child. Pop 3: count 3, which equals k, so the answer is 3, and the nodes 4, 5 and 6 are never touched.

    Step by step

    1. Create stack as an empty [TreeNode] with var. append pushes and removeLast() pops, so an array works as a stack.
    2. Declare node with var as root. It is the root of the part of the tree still to be walked. When it is nil, the next node to visit is on top of the stack.
    3. Declare count at 0: the number of nodes visited so far.
    4. Loop while node != nil || !stack.isEmpty. The traversal is over only when nothing is left to descend into and nothing waits on the stack.
    5. Descend left with an inner while let current = node.
    6. Push current. Its left subtree holds smaller values, so it waits.
    7. Set node to current.left.
    8. After the inner loop, pop the last node into a new constant current with removeLast(). It is the smallest unvisited value.
    9. Visit it: count += 1.
    10. Check count == k. If so, return current.val. The rest of the tree is never visited.
    11. Otherwise go right: set node to current.right, so the next pass walks through that subtree. Its values are bigger than current but smaller than the nodes still on the stack.
    12. Return -1 after the loop. Since k is at most the number of nodes, this line never runs, but Swift requires a return on every path.

    The solution in Swift

    class Solution {
        func kthSmallest(_ root: TreeNode?, _ k: Int) -> Int {
            var stack: [TreeNode] = []
            var node = root
            var count = 0
            while node != nil || !stack.isEmpty {
                while let current = node {
                    stack.append(current)
                    node = current.left
                }
                let current = stack.removeLast()
                count += 1
                if count == k {
                    return current.val
                }
                node = current.right
            }
            return -1
        }
    }
    

    Swift details worth knowing:

    • Two variables called current. The one in while let current = node exists only inside that inner loop. After the loop ends, let current = stack.removeLast() declares a new constant in the outer loop's scope, which is why the same name can be reused.
    • removeLast() returns the element. It both removes the last item and gives it back, and it crashes on an empty array. Here the stack can't be empty at that point: if node is nil and the loop condition holds, the stack has something in it, and if node wasn't nil, the inner loop pushed at least one node.
    • Arrays of class instances. [TreeNode] stores references, so pushing a node doesn't copy it.
    • Optionals in a condition. node != nil compares an optional with nil without unwrapping it, and the stack check uses the isEmpty property, which is clearer than stack.count == 0.

    Complexity

    Time: O(h + k), where h is the height of the tree. The first descent costs up to h pushes, and after that each of the k visits pops one node and pushes the nodes of a left chain. Every node is pushed and popped at most once, so the walk never exceeds O(n).

    Space: O(h). The stack holds the path from the root down to the current node, at most h nodes.

    Edge cases and common mistakes

    • A single node. The tree with only 7 and k = 1 pushes the node, pops it, counts 1 and returns 7.
    • k = 1. The answer is the leftmost node, found after the first descent.
    • k equal to the number of nodes. The walk visits every node and returns the largest value, the rightmost one.
    • A skewed tree. For a chain going only left, the first descent pushes every node (the stack depth is n), and then the pops run in increasing order.
    • Counting when pushing. Pushes are not visits. Count a node when it is popped, in sorted order, not when you first reach it.
    • Forgetting to go right. After a visit, the node's right subtree contains the next larger values. Without node = current.right they are skipped.
    • Counting from 0. k starts at 1, and count starts at 0, so comparing after incrementing is right.

    Testing

    • Root 3 with children 1 and 4 and 2 under 1, k = 1 gives 1.
    • Root 5 with children 3 and 6, then 2 and 4 under 3 and 1 under 2: k = 3 gives 3, k = 1 gives 1 and k = 6 gives 6.
    • For the same tree, k = 2 gives 2, k = 4 gives 4 and k = 5 gives 5.
    • A single node holding 7, k = 1, gives 7.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free