Medium
题目描述
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
示例 1:
输入:root = [3,1,4,null,2], k = 1
输出:1
示例 2:
输入:root = [5,3,6,2,4,null,null,1], k = 3
输出:3
提示:
- 树中的节点数为
n 1 <= k <= n <= 10^40 <= Node.val <= 10^4
进阶: 如果二叉搜索树经常被修改(插入/删除操作)并且你需要频繁地查找第 k 小的值,你将如何优化算法?
提示:
- 尝试利用二叉搜索树的性质
- 尝试中序遍历
- 如果可以修改二叉搜索树节点的结构呢?
- 最优的运行时间复杂度是 O(BST 的高度)
解题思路
解题思路
这道题要求在二叉搜索树中找到第 k 小的元素。由于 BST 的性质(左子树所有节点值 < 根节点值 < 右子树所有节点值),我们可以利用这个特性来解决问题。
方法一:中序遍历(推荐)
BST 的中序遍历会按照从小到大的顺序访问所有节点。因此,我们只需要进行中序遍历,当访问到第 k 个节点时,该节点的值就是答案。为了提高效率,我们可以在找到第 k 个节点后立即返回,而不需要遍历整棵树。
方法二:迭代中序遍历
使用栈来模拟递归的中序遍历过程,同样在访问第 k 个节点时返回结果。
方法三:分治法(最优)
利用 BST 的性质,我们可以通过统计左子树的节点数来判断第 k 小的元素在哪个子树中:
- 如果左子树节点数 ≥ k,则第 k 小的元素在左子树
- 如果左子树节点数 = k-1,则根节点就是第 k 小的元素
- 否则,第 k 小的元素在右子树,且是右子树中第 (k - 左子树节点数 - 1) 小的元素
这里我们采用中序遍历的递归实现,因为代码简洁且易于理解。
代码实现
class Solution {
private:
int count = 0;
int result = 0;
void inorder(TreeNode* root, int k) {
if (!root) return;
inorder(root->left, k);
count++;
if (count == k) {
result = root->val;
return;
}
inorder(root->right, k);
}
public:
int kthSmallest(TreeNode* root, int k) {
count = 0;
inorder(root, k);
return result;
}
};
class Solution:
def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
def inorder(node):
if not node:
return []
return inorder(node.left) + [node.val] + inorder(node.right)
# 简化版本:直接返回第k-1个元素
return inorder(root)[k-1]
# 优化版本(提前返回):
# self.count = 0
# self.result = 0
# def inorder_optimized(node):
# if not node:
# return
# inorder_optimized(node.left)
# self.count += 1
# if self.count == k:
# self.result = node.val
# return
# inorder_optimized(node.right)
# inorder_optimized(root)
# return self.result
public class Solution {
private int count = 0;
private int result = 0;
public int KthSmallest(TreeNode root, int k) {
count = 0;
Inorder(root, k);
return result;
}
private void Inorder(TreeNode root, int k) {
if (root == null) return;
Inorder(root.left, k);
count++;
if (count == k) {
result = root.val;
return;
}
Inorder(root.right, k);
}
}
var kthSmallest = function(root, k) {
let count = 0;
let result = 0;
function inorder(node) {
if (!node || count >= k) return;
inorder(node.left);
count++;
if (count === k) {
result = node.val;
return;
}
inorder(node.right);
}
inorder(root);
return result;
};
复杂度分析
| 算法 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 中序遍历(递归) | O(H + k) | O(H) |
| 中序遍历(迭代) | O(H + k) | O(H) |
其中 H 是树的高度。在最坏情况下(树退化为链表),H = n;在最好情况下(平衡二叉树),H = log n。
注意: 这里的时间复杂度是 O(H + k) 而不是 O(n),因为我们在找到第 k 个元素后就停止遍历了。