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):

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

这种方式的关键点:

  • 正确的计数时机: 在「访问当前节点」时 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
相关推荐
小七在进步12 分钟前
类和对象(五)
java·开发语言·算法
鑫 源18 分钟前
VS2019 for mac里用c#开发Mac OS应用 避坑 和 源码
职场和发展·蓝桥杯
就叫年华吧丶21 分钟前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue
FPGA信号处理28 分钟前
【信号检测与估计】第四节课:非高斯噪声下的 BLUE、极大似然与 EM 算法
人工智能·算法·机器学习
.道阻且长.1 小时前
C++ 11:可变参数模板
前端·c++·算法
Doubbbbbbble云3 小时前
基于贪心算法的区间覆盖问题求解策略4
算法·贪心算法
不会就选b9 小时前
算法日常・每日刷题--<贪心>25
数据结构·算法
西部驯兽师11 小时前
制造企业的信息化选型课题(一)
职场和发展
Cccp.12313 小时前
【leetcode】(七)前缀树和贪心算法
数据结构·leetcode·贪心算法
天真小巫13 小时前
2026.9.28总结
职场和发展