{x}
blog image

Kth Smallest Element in a BST

Given the root of a binary search tree, and an integer k, return the kth smallest value (1-indexed) of all the values of the nodes in the tree.

 

Example 1:

Input: root = [3,1,4,null,2], k = 1
Output: 1

Example 2:

Input: root = [5,3,6,2,4,null,null,1], k = 3
Output: 3

 

Constraints:

  • The number of nodes in the tree is n.
  • 1 <= k <= n <= 104
  • 0 <= Node.val <= 104

 

Follow up: If the BST is modified often (i.e., we can do insert and delete operations) and you need to find the kth smallest frequently, how would you optimize?

Solution Explanation

The problem asks to find the k-th smallest element in a Binary Search Tree (BST). We can solve this using two main approaches: iterative inorder traversal and a more optimized approach using the properties of a BST.

Approach 1: Iterative Inorder Traversal

This approach leverages the property of inorder traversal in a BST: Inorder traversal visits nodes in ascending order. We use an iterative approach (using a stack) to perform inorder traversal efficiently. We stop the traversal when we've encountered the k-th smallest element.

Algorithm:

  1. Initialize an empty stack stk.
  2. While the current node root is not NULL or the stack is not empty:
    • If root is not NULL: push root onto the stack and move to its left child (root = root.left).
    • Otherwise (if root is NULL): pop the top node from the stack. Decrement k. If k becomes 0, the popped node's value is the k-th smallest element, so return its value. Then, move to the right child of the popped node (root = root.right).

Code (Python):

class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        stk = []
        while root or stk:
            if root:
                stk.append(root)
                root = root.left
            else:
                root = stk.pop()
                k -= 1
                if k == 0:
                    return root.val
                root = root.right

Time Complexity Analysis: O(H + k), where H is the height of the BST. In the worst case (a skewed tree), H can be equal to N (number of nodes), resulting in O(N) time complexity. However, for balanced BSTs, H is log(N), and the complexity becomes closer to O(log N + k). In the best case, the k-th smallest element might be near the root, leading to O(k) time. The k factor comes from the potential need to traverse up to k nodes during the inorder traversal.

Space Complexity Analysis: O(H) in the worst case (skewed tree), which becomes O(log N) for a balanced BST, due to the stack's size being proportional to the height of the tree.

Approach 2: Optimized BST Approach (Using Node Counts)

This approach is more sophisticated and leverages the inherent structure of a BST. We pre-compute the number of nodes in the subtree rooted at each node. Then, we can efficiently navigate the tree to find the k-th smallest element.

Algorithm:

  1. Create a BST class that precomputes the node counts for each node in the tree.
  2. Implement a kthSmallest method within the BST class to efficiently find the k-th smallest element using the precomputed node counts.

Code (Python):

from collections import Counter
 
class BST:
    def __init__(self, root):
        self.cnt = Counter()
        self.root = root
        self.count(root)
 
    def kthSmallest(self, k):
        node = self.root
        while node:
            if self.cnt[node.left] == k - 1:
                return node.val
            if self.cnt[node.left] < k - 1:
                k -= self.cnt[node.left] + 1
                node = node.right
            else:
                node = node.left
        return 0
 
    def count(self, root):
        if root is None:
            return 0
        n = 1 + self.count(root.left) + self.count(root.right)
        self.cnt[root] = n
        return n
 
 
class Solution:
    def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
        bst = BST(root)
        return bst.kthSmallest(k)

Time Complexity Analysis: O(N) for pre-computation of node counts and O(log N) for finding the k-th smallest element (in a balanced tree). For a skewed tree, the search can still take O(N) time.

Space Complexity Analysis: O(N) to store the node counts in the cnt dictionary (hash map).

Conclusion:

Approach 1 (iterative inorder traversal) is generally simpler to implement. Approach 2 offers better performance if you need to repeatedly query for k-th smallest elements on the same BST. The choice depends on the specific application and whether repeated queries are expected. Both approaches are provided with examples in several programming languages.