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:
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?
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:
stk
.root
is not NULL
or the stack is not empty:
root
is not NULL
: push root
onto the stack and move to its left child (root = root.left
).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:
BST
class that precomputes the node counts for each node in the tree.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.