题目来源:改编自 LeetCode 98. 验证二叉搜索树。本 Lab 使用课程自定义输入输出协议和独立测试,不复制来源站点的代码或测试。
学习目标
前置知识与环境
先阅读第 8.2 节 二叉排序树的"定义与不变量"一节:BST 的不变量作用在整棵子树上,这正是本 Lab 递归传界写法的出处。
在 Lab 目录执行 make doctor 检查环境;未安装 Make 时可在仓库根使用 pnpm 兜底命令(见下文"运行")。
任务
给定一棵以编号 0 为根的二叉树(n = 0 时为空树),判定它是否为二叉排序树。
教材口径:BST 中关键字互不相同,任一结点左子树的所有关键字严格小于它,右子树的所有关键字严格大于它——只要出现相等关键字即非法;空树是 BST。
输入格式
- 第一行:一个整数
n(结点数,可以为 0); - 接下来
n行:每行四个整数i val l r——结点编号i(0 ≤ i < n)、关键字val、左孩子编号l与右孩子编号r(不存在为-1)。n > 0时编号 0 是根,输入保证这些指针构成一棵以 0 为根的二叉树。
输出格式
一行一个整数:是 BST 输出 1,否则输出 0。
数据范围与限制
| 项目 | 范围 |
|---|---|
结点数 n | 0 ≤ n ≤ 10⁴ |
关键字 val | −10¹⁸ ≤ val ≤ 10¹⁸(必须用 64 位整数读取) |
| 结点编号 | 0 ≤ i < n;孩子不存在时为 −1 |
| 时间复杂度要求 | 每个结点只访问常数次, |
| 输出上限 | 1 MB(单个整数,默认上限足够) |
正常、边界与错误情况
- 正常:每个结点的整棵左右子树都落在祖先约束的范围内,而不只是与父结点比较;
- 边界:
n = 0(空树,输出1);单结点;关键字恰好等于 ±10¹⁸ 的深层结点; - 违规:孙辈结点违反祖先约束、但每个父子局部关系都合法(只比较父子的写法会误判为合法);出现相等关键字(按教材口径即非法)。
样例
样例 1
input
5
0 50 1 2
1 30 3 4
2 70 -1 -1
3 20 -1 -1
4 40 -1 -1output
1左子树 {30, 20, 40} 全部小于 50,右子树 {70} 大于 50;30 的左孩子 20、右孩子 40 局部与全局都合法。
样例 2
input
3
0 50 1 -1
1 30 -1 2
2 60 -1 -1output
030 < 50(父子的左孩子关系合法),60 > 30(30 的右孩子关系也合法),但 60 位于根 50 的左子树内却大于 50,违反 BST。只比较父子结点的检查在这里会误判为合法。
实现提示
- 递归时向子树传递开区间
(low, high):当前结点必须满足low < val < high,递归左子树时把上界收紧为val,递归右子树时把下界收紧为val; - 哨兵初值用
long long的极限值(<climits>中的LLONG_MIN/LLONG_MAX),不要用 ±10¹⁸ 当哨兵——结点关键字本身可以恰好取到 ±10¹⁸; - 严格比较(
val <= low或val >= high判非法)自动排除相等关键字;等价做法是"中序遍历严格递增"。
运行
powershell
make run
# 免 Make 兜底:在仓库根执行
pnpm lab:run -- labs/chapter-08/exercise/E-08-06-validate-bst需要单用例或严格评分时使用 make run CASE=007-stress-illegal 与 make score;也可用 pnpm lab:score -- labs/chapter-08/exercise/E-08-06-validate-bst 兜底。
完成清单
思考与复盘
- "传递上下界"与"中序严格递增"两种验证各自维持什么不变量?为什么中序法只需比较相邻两次输出?
- 如果把口径放宽成"左 ≤ 根 ≤ 右"(允许相等),哪些判定分支要改?相等关键字会破坏 BST 插入与删除的哪些操作?
- 结点数 10⁴ 的单链树递归深度可达 10⁴;什么情况下需要改写成显式栈的迭代版本?