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
相关推荐
那个村的李富贵12 小时前
CANN加速下的AIGC“即时翻译”:AI语音克隆与实时变声实战
人工智能·算法·aigc·cann
power 雀儿12 小时前
Scaled Dot-Product Attention 分数计算 C++
算法
琹箐13 小时前
最大堆和最小堆 实现思路
java·开发语言·算法
renhongxia113 小时前
如何基于知识图谱进行故障原因、事故原因推理,需要用到哪些算法
人工智能·深度学习·算法·机器学习·自然语言处理·transformer·知识图谱
坚持就完事了13 小时前
数据结构之树(Java实现)
java·算法
算法备案代理13 小时前
大模型备案与算法备案,企业该如何选择?
人工智能·算法·大模型·算法备案
赛姐在努力.13 小时前
【拓扑排序】-- 算法原理讲解,及实现拓扑排序,附赠热门例题
java·算法·图论
野犬寒鸦15 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
霖霖总总15 小时前
[小技巧66]当自增主键耗尽:MySQL 主键溢出问题深度解析与雪花算法替代方案
mysql·算法
rainbow688915 小时前
深入解析C++STL:map与set底层奥秘
java·数据结构·算法