LeetCode 230:二叉搜索树中第 K 小的元素 —— 从 Inorder 遍历到 Order Statistic Tree

题目回顾

给定一棵二叉搜索树(BST)的根节点 root 和一个整数 k,返回这棵树中第 k 小的节点值(1-indexed)。这道题是 LeetCode 230「Kth Smallest Element in a BST」,也经常出现在面试中。algo+1​

因为是 BST,它满足「左子树所有值 < 根节点 < 右子树所有值」的性质,这一点是设计解法的关键。takeuforward

初始误区:preorder 能不能做?

一个很自然的想法是:「随便用一种遍历方式(比如 preorder),在遍历过程中数到第 k 个节点时返回它。」

但是在 BST 里,只有 inorder 遍历(左--根--右)才能保证访问顺序是从小到大;preorder(根--左--右)得到的序列一般不是有序的,所以用 preorder 数到的第 k 个节点并不等于第 k 小的节点。这是第一个需要修正的点。heycoach+1​

总结一下:

  • BST + inorder → 访问顺序是按值从小到大。
  • 想要「第 k 小」,就要在 inorder 过程中数第 k 次访问到的节点,而不是随便选一种遍历。algo+1

正确核心思路:递归 inorder + 外部计数

利用 inorder 的有序性,一个直接的解法是:递归做 inorder 遍历,同时维护一个外部计数器 count,当访问到第 k 个节点时记录答案并提前结束遍历。finalroundai+1​

伪代码思路大致如下(语言无关):

  1. 准备外部变量:

    • count:已经访问过的节点数,初始为 0
    • ans:答案
    • found:是否已经找到第 k 小,初始为 false
  2. 定义递归函数 inorder(node)

    • 如果 nodenull 或者 found 已经为 true,直接返回(提前剪枝)。
    • 递归访问左子树。
    • 访问当前节点时:count++;如果 count == k,则记录 ans = node.valfound = truereturn
    • 递归访问右子树。

这种方式的关键点:

  • 正确的计数时机: 在「访问当前节点」时 count++(即左子树处理完之后)。
  • 注意 off-by-one: 题目给的是 1-indexed,第 1 小就是第一次访问到的节点,所以条件是 count == k,而不是 k+1。finalroundai+1

时间复杂度和空间复杂度:

  • 最坏情况下 ,要走到第 k 个节点,时间复杂度是 O(h + k) ,h 为树高,最坏 O(n);因为一旦找到第 k 个就可以停止,不必遍历整棵树。heycoach+1
  • 使用递归,递归栈的深度与树高 h 成正比,所以空间复杂度O(h) ,最坏也是 O(n)。algomaster+1

**如果你更喜欢迭代写法,**可以用显式栈模拟 inorder,复杂度同样是 O(h + k) 时间、O(h) 额外空间。codecraftbyprasad+1​

Follow-up:频繁修改时怎么优化?

Follow-up 问的是更偏「数据结构设计」的问题:如果这棵 BST 经常执行插入、删除,并且频繁查询第 k 小,用每次 inorder 从头数的方式就不够高效了,因为每次查询仍然可能接近 O(n)。stackoverflow+1​

更好的思路是「给 BST 做扩展(augment)」:

  1. 在每个节点上增加一个字段 size,表示「以该节点为根的子树一共有多少个节点」。

  2. 维护规则:

    • 插入、删除节点时,沿路径回溯更新 size(和普通 BST 维护高度一样的思路)。
    • 这样任意节点的 size 都可以在 O(1) 时间读到。
  3. **查询第 k 小的流程(类似 order statistic tree):**wikipedia+2​

    • 记当前节点为 x,左子树大小为 L = size(x.left)(如果为空则为 0)。
    • 如果 k == L + 1,说明当前节点就是第 k 小,直接返回 x
    • 如果 k ≤ L,说明第 k 小在左子树里,递归/迭代到左子树继续找。
    • 如果 k > L + 1,说明第 k 小在右子树里,转到右子树,同时寻找的是「右子树中的第 (k − L − 1) 小」。

在一棵高度为 h 的树上,这样的查询只沿着从根到叶的一条路径走,所以查询时间是 O(h) ;如果再配合自平衡树(如 AVL、红黑树),就可以保证 h 为 O(log n) ,从而增删查(包括第 k 小)都是 O(log n)。heycoach+2​

小结

这道题的关键不在于「怎么写遍历」,而在于「如何利用 BST 的有序结构」:

  • 基础版: 用 inorder + 外部计数/参数计数,在访问到第 k 个节点时返回,时间 O(h + k),空间 O(h)。algo+1
  • 进阶版: 通过在每个节点存子树大小,把普通 BST 扩展成 order statistic tree,使查询第 k 小可以在 O(h) 时间完成,并且在自平衡的前提下稳定达到 O(log n)。stackoverflow+1
相关推荐
jyyyx的算法博客29 分钟前
多模字符串匹配算法 -- 面试题 17.17. 多次搜索
算法
da_vinci_x31 分钟前
Sampler AI + 滤波算法:解决 AIGC 贴图“噪点过剩”,构建风格化 PBR 工业管线
人工智能·算法·aigc·材质·贴图·技术美术·游戏美术
惊鸿.Jh33 分钟前
503. 下一个更大元素 II
数据结构·算法·leetcode
chao18984439 分钟前
MATLAB 实现声纹识别特征提取
人工智能·算法·matlab
zhishidi41 分钟前
推荐算法之:GBDT、GBDT LR、XGBoost详细解读与案例实现
人工智能·算法·推荐算法
货拉拉技术42 分钟前
货拉拉RAG优化实践:从原始数据到高质量知识库
数据库·算法
测试界茜茜1 小时前
独立搭建UI自动化测试框架分享
自动化测试·软件测试·功能测试·程序人生·ui·职场和发展
AKDreamer_HeXY1 小时前
ABC434E 题解
c++·算法·图论·atcoder
罗湖老棍子1 小时前
完全背包 vs 多重背包的优化逻辑
c++·算法·动态规划·背包