二叉搜索树(Binary Search Tree, BST)是一种特殊的二叉树数据结构,它具有以下关键性质:
这个特性带来一个极其重要的性质:中序遍历BST会得到一个升序排列的序列。中序遍历的顺序是"左子树 → 根节点 → 右子树",正好符合BST节点值从小到大的排列规则。
提示:理解这个特性是解决本题的关键。BST的中序遍历结果就是所有节点值的有序排列,因此第k小的元素就是中序遍历序列中的第k个元素。
在实际应用中,BST常用于实现高效的查找、插入和删除操作,平均时间复杂度都是O(log n)。这道LeetCode题目正是考察我们对BST特性的理解以及遍历算法的掌握程度。
题目给定一个BST的根节点和一个整数k(k从1开始计数),要求返回树中第k小的元素值。我们需要考虑以下边界条件:
基于BST的中序遍历特性,我们可以得出以下解题框架:
这个思路可以衍生出两种具体实现方式:迭代法和递归法。下面我们分别详细解析这两种实现。
迭代法中序遍历使用显式的栈结构来模拟递归调用的过程,避免了递归带来的栈溢出风险。这在处理深度很大的树时尤为重要。迭代法的核心思想是:
javascript复制function kthSmallest(root, k) {
const stack = [];
let curr = root;
while (curr || stack.length) {
// 将当前节点及其所有左子节点压入栈
while (curr) {
stack.push(curr);
curr = curr.left;
}
// 弹出栈顶节点(当前最小节点)
curr = stack.pop();
// 检查是否找到第k小的元素
if (--k === 0) {
return curr.val;
}
// 转向右子树
curr = curr.right;
}
return -1; // 题目保证k有效,这里只是形式上的返回
}
代码解析:
时间复杂度:O(h + k),其中h是树的高度。最坏情况下(斜树)为O(n),最好情况下(平衡树)为O(log n + k)。
空间复杂度:O(h),由栈的深度决定。最坏情况下为O(n),最好情况下为O(log n)。
优化点:
递归法中序遍历更符合我们对BST遍历的直观理解,代码也更加简洁。其核心思想是:
递归法的优势在于代码简洁明了,但在处理深度很大的树时可能会引发栈溢出。
javascript复制function kthSmallest(root, k) {
let result;
function inorder(node) {
if (!node || k === 0) return;
inorder(node.left); // 遍历左子树
if (--k === 0) { // 访问当前节点
result = node.val;
return;
}
inorder(node.right); // 遍历右子树
}
inorder(root);
return result;
}
代码解析:
时间复杂度:与迭代法相同,O(h + k)。
空间复杂度:O(h),主要是递归调用栈的空间。
注意事项:
| 特性 | 迭代法 | 递归法 |
|---|---|---|
| 代码复杂度 | 中等,需要手动维护栈 | 简单,直接反映算法逻辑 |
| 空间复杂度 | O(h)(显式栈) | O(h)(调用栈) |
| 栈溢出风险 | 无 | 树深度大时可能发生 |
| 适用场景 | 生产环境,大数据量 | 算法题解,小规模数据 |
| 调试难度 | 较难,需要跟踪栈状态 | 较易,调用栈清晰 |
面试场景:优先选择递归法,代码简洁明了,易于解释。但需要说明其潜在的栈溢出问题。
生产环境:推荐使用迭代法,特别是处理不确定大小的树结构时,避免栈溢出风险。
极端情况:如果树可能非常深(如超过10000层),必须使用迭代法。
代码维护:团队协作时,迭代法虽然代码稍长,但行为更可预测,更易于维护。
k的计数错误:
栈处理不完整:
递归终止条件缺失:
小规模测试:
边界测试:
遍历过程可视化:
多次查询优化:
平衡BST的优势:
并行遍历可能性:
BST中第K大的元素:
验证BST的有效性:
BST转换为累加树:
数据库索引:
排行榜系统:
自动补全系统:
如果BST节点中维护了子树大小的信息,如何优化查询?
如何处理频繁插入/删除后的第K小查询?
在分布式环境下如何实现这类查询?