230. Kth Smallest Element in a BST
Problem Description
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 <= 10⁴0 <= Node.val <= 10⁴
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 solution?
Solution
Python Solution
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
# Iterative inorder traversal
stack = []
curr = root
while curr or stack:
# Traverse to leftmost node
while curr:
stack.append(curr)
curr = curr.left
# Process current node
curr = stack.pop()
k -= 1
if k == 0:
return curr.val
# Move to right subtree
curr = curr.right
Time Complexity: O(H + k)
Where H is the height of the tree. In the worst case, we need to visit H nodes to reach the leftmost leaf, then k nodes to find the kth element.
Space Complexity: O(H)
Where H is the height of the tree, used by the stack.
Java Solution
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int kthSmallest(TreeNode root, int k) {
// Iterative inorder traversal
Stack stack = new Stack<>();
TreeNode curr = root;
while (curr != null || !stack.isEmpty()) {
// Traverse to leftmost node
while (curr != null) {
stack.push(curr);
curr = curr.left;
}
// Process current node
curr = stack.pop();
k--;
if (k == 0) {
return curr.val;
}
// Move to right subtree
curr = curr.right;
}
return -1; // Should never reach here if k is valid
}
}
Time Complexity: O(H + k)
Where H is the height of the tree. In the worst case, we need to visit H nodes to reach the leftmost leaf, then k nodes to find the kth element.
Space Complexity: O(H)
Where H is the height of the tree, used by the stack.
C++ Solution
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
// Iterative inorder traversal
stack stack;
TreeNode* curr = root;
while (curr || !stack.empty()) {
// Traverse to leftmost node
while (curr) {
stack.push(curr);
curr = curr->left;
}
// Process current node
curr = stack.top();
stack.pop();
k--;
if (k == 0) {
return curr->val;
}
// Move to right subtree
curr = curr->right;
}
return -1; // Should never reach here if k is valid
}
};
Time Complexity: O(H + k)
Where H is the height of the tree. In the worst case, we need to visit H nodes to reach the leftmost leaf, then k nodes to find the kth element.
Space Complexity: O(H)
Where H is the height of the tree, used by the stack.
JavaScript Solution
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @param {number} k
* @return {number}
*/
var kthSmallest = function(root, k) {
// Iterative inorder traversal
const stack = [];
let curr = root;
while (curr || stack.length) {
// Traverse to leftmost node
while (curr) {
stack.push(curr);
curr = curr.left;
}
// Process current node
curr = stack.pop();
k--;
if (k === 0) {
return curr.val;
}
// Move to right subtree
curr = curr.right;
}
return -1; // Should never reach here if k is valid
};
Time Complexity: O(H + k)
Where H is the height of the tree. In the worst case, we need to visit H nodes to reach the leftmost leaf, then k nodes to find the kth element.
Space Complexity: O(H)
Where H is the height of the tree, used by the stack.
C# Solution
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
public class Solution {
public int KthSmallest(TreeNode root, int k) {
// Iterative inorder traversal
var stack = new Stack();
TreeNode curr = root;
while (curr != null || stack.Count > 0) {
// Traverse to leftmost node
while (curr != null) {
stack.Push(curr);
curr = curr.left;
}
// Process current node
curr = stack.Pop();
k--;
if (k == 0) {
return curr.val;
}
// Move to right subtree
curr = curr.right;
}
return -1; // Should never reach here if k is valid
}
}
Time Complexity: O(H + k)
Where H is the height of the tree. In the worst case, we need to visit H nodes to reach the leftmost leaf, then k nodes to find the kth element.
Space Complexity: O(H)
Where H is the height of the tree, used by the stack.
Approach Explanation
The solution uses an iterative inorder traversal of the BST. Here's how it works:
- Inorder Traversal Process:
- Use a stack to keep track of nodes
- Traverse to the leftmost node first
- Process nodes in order: left, root, right
- Stop when we find the kth element
- Key Properties:
- Inorder traversal of BST visits nodes in ascending order
- No need to traverse the entire tree once we find kth element
For the follow-up question about frequent modifications and queries: We could maintain additional information in each node:
- Count of nodes in left subtree
- This allows O(H) time for both updates and queries
- Trade-off is additional space and maintenance during modifications