二叉搜索树第K小元素 · 多解法全解析
一、 题目梗概与核心破局点
题目描述:给定一个二叉搜索树(BST)的根节点 root,和一个整数 k,请你设计一个算法查找其中 第 k 小 的元素(k 从 1 开始计数)。
图片图片
破局点分析:为什么是 BST?
二叉搜索树有着严格的“家族族规”:任何节点,其 左子树的数字必然小于它,右子树的数字必然大于它。基于这个特性,只要我们按照 左子树 -> 根节点 -> 右子树 的顺序(即 中序遍历)去访问所有节点,读出来的数字序列 天然就是从小到大排好序的!
二、 算法流派演进
关于这道题,我们提炼出了三大核心编码流派,带你从“暴力小白”一步步进阶为“大厂极客”。
✦
📝 流派一:暴力全收集法
逻辑最简单 性能较差
思路解析:完全不考虑提前停止。像抄作业一样,利用中序遍历把树里所有的数字全部抄到一个列表里。等整棵树抄完了,直接去列表里拿索引为 k-1 的数字。
✅ 优点
心智负担极低,只需写出最标准的树遍历模板,直接交卷,不易出错。
❌ 缺点
严重浪费资源。哪怕只找第 1 小的数,它也要遍历 10000 个节点并申请相应的内存空间,时间空间复杂度均为 O(N)。
🔥 源码展示(Python 版):
classSolution:
defkthSmallest(self, root: Optional[TreeNode], k: int) -> int:
res = [] # 准备一个空列表充当记事本
definorder(node):
if not node: return
inorder(node.left) # 1. 探底左子树
res.append(node.val) # 2. 不管三七二十一,全抄下来
inorder(node.right) # 3. 探底右子树
inorder(root)
return res[k - 1] # 列表从 0 开始,所以取 k-1
✦
🎯 流派二:递归截断法
效率最优 面试推荐
思路解析:这是上一流派的升级版。我们在遍历的过程中挂一个 “计数器”。每看一个数字,计数器减 1;当计数器归零时,说明已经找到了第 k 个数,立刻强行刹车,层层向上汇报,不再遍历剩下的树。
✅ 优点
找到即停!如果 k 很小,程序可以在极短时间内下班,时间复杂度降为 O(H + k),无需额外开辟大规模数组存放数据。
❌ 缺点
在递归逻辑中传递状态变量较为繁琐,初学者常常会在 Python 的全局变量定义或 C++ 的递归深度上踩坑。
🔥 源码展示(C++ & Python 双打版):
// --- C++ 标准答案 ---
classSolution {
private:
int count = 0;
int result = 0;
public:
intkthSmallest(TreeNode* root, int k) {
inorder(root, k);
return result;
}
voidinorder(TreeNode* node, int k) {
// 如果节点为空,或答案已找到,立刻截断返回
if (node == nullptr || count >= k) return;
inorder(node->left, k); // 左
if (++count == k) { // 中:计数器推进并验证
result = node->val;
return;
}
inorder(node->right, k); // 右
}
};
--- Python 纯净版 ---
classSolution:
defkthSmallest(self, root: Optional[TreeNode], k: int) -> int:
self.k = k
self.result = None
defdfs(node):
这里必须用 is not None 严谨校验
if not node or self.result is not None:
return
dfs(node.left)
self.k -= 1
if self.k == 0:
self.result = node.val
return
dfs(node.right)
dfs(root)
return self.result
✦
🧱 流派三:非递归迭代法
脱离递归 进阶基础
思路解析:面试官常常设下陷阱:“如果系统不允许深度递归,会引发爆栈,你该如何遍历?”。这套解法的核心是 手动维护一个“栈(Stack)” 来模拟计算机底层的递归调用路线。
点评
代码相对难懂。思路是:一条死路往左走到黑,沿途将所有节点扔进桶(栈)中。撞墙后,从桶顶捞出最新放入的节点(即当前的极小值),记录并转向该节点的右侧继续。虽然复杂,但能展现你对计算机底层执行机制的深刻理解。
classSolution:
defkthSmallest(self, root: Optional[TreeNode], k: int) -> int:
stack = []
while root or stack:
1. 疯狂往左钻,沿途留下记号在栈中
while root:
stack.append(root)
root = root.left
2. 撞南墙后,捞出最近的一个记号(当前最小节点)
root = stack.pop()
k -= 1
if k == 0:
return root.val
3. 转向该节点的右侧分支
root = root.right