第5章|二叉树与递归

Unit 00 · 前置心智模型

递归不是“自己调用自己”,
而是对子问题作出可信承诺。

当前函数只负责一棵子树:把左右子树交给同样定义的递归调用,暂停等待返回值,再根据函数契约组合出当前节点的答案。

CHAPTER 05 / THE COGNITIVE ATLAS

第 5 章 · 认知梯度全景图

二叉树与递归:从递归的返回值出发,建立树上的分解与合并思路。

8 个后续单元
贯穿本章的统一框架定义一次调用的含义 → 确定空节点基例 → 组合孩子结果 → 区分返回值与全局更新。

从基础到复合任务,每一步增加一种决策

这里的梯度描述认知任务的递进,不等同于题库的难度标签。点击题目可进入对应单元。

起点 · Unit 00 二叉树与递归的心智模型。先理解状态、操作与不变量,再沿下列路线练习。
  1. 01

    单树返回与修改

    从返回子树深度,到返回并重连已处理的子树。

  2. 02

    双节点比较

    明确镜像位置与同向位置,统一双空、单空和数值检查。

  3. 03

    搜索加验证

    把候选起点搜索和局部结构验证拆成两种契约。

  4. 04

    单支返回、双支更新

    父节点只能连接一支,穿过当前节点的全局路径可连接两支。

  5. 05

    路径历史与恢复

    引入活动祖先链的前缀频次,离开分支时撤销历史。

每道题在框架中的具体形式

用同一组问题定位每个单元,再观察迁移题改变了哪个条件。进入写代码环节后,学习地图会收起,留出独立思考的空间。

UNIT 01单树返回与修改

#104 二叉树的最大深度

子问题契约
一次调用返回以当前节点为根的最大深度。
分解与合并
读取左右子树深度,取较大者并计入当前节点。
返回与副作用
返回的是完整子树的摘要,不需要全局答案。
边界检查
空树深度为 0,叶子深度为 1;计数单位为节点。
迁移 · #111 二叉树的最小深度最小深度只允许沿真实存在的孩子到叶子,单空孩子不能取最小。
先说清契约,再独立实现进入本单元 ↗
UNIT 02单树返回与修改

#226 翻转二叉树

子问题契约
一次调用返回当前子树的镜像根。
分解与合并
先得到原左右子树的镜像,再交换连接。
返回与副作用
修改发生在当前节点;返回根以便父节点重连。
边界检查
空节点原样返回,不丢失原左右子树入口。
迁移 · #101 对称二叉树对称树由修改单树变为比较两棵子树是否互为镜像。
先说清契约,再独立实现进入本单元 ↗
UNIT 03双节点比较

#101 对称二叉树

子问题契约
一次双节点调用判断两棵子树是否互为镜像。
分解与合并
值相等后,交叉比较左与右、右与左。
返回与副作用
布尔结果覆盖两侧完整子树,两个递归条件都必须成立。
边界检查
双空成立,单空失败;形状和值都参与比较。
迁移 · #100 相同的树相同树保留基例和值检查,将交叉位置换成同向位置。
先说清契约,再独立实现进入本单元 ↗
UNIT 04双节点比较

#100 相同的树

子问题契约
一次双节点调用判断两个对应子树是否完全相同。
分解与合并
值相等后,同向比较左右孩子。
返回与副作用
返回结构与数值同时一致的布尔结果。
边界检查
双空成立,单空失败,不能只比较遍历数值序列。
迁移 · #572 另一棵树的子树子树判断在大树中搜索起点,再复用相同树验证器。
先说清契约,再独立实现进入本单元 ↗
UNIT 05搜索加验证

#572 另一棵树的子树

子问题契约
外层判断区域中是否存在目标子树,内层判断完整结构是否相同。
分解与合并
当前起点验证失败后,继续搜索左、右子树中的起点。
返回与副作用
外层使用存在关系,内层使用全部相同关系。
边界检查
候选节点值相等仍不代表整棵子树匹配。
迁移 · #1367 二叉树中的链表树中链表把内层验证改成向下路径匹配,外层搜索仍保留。
先说清契约,再独立实现进入本单元 ↗
UNIT 06单支返回、双支更新

#543 二叉树的直径

子问题契约
返回从当前节点向下的最大单支深度。
分解与合并
当前节点左右深度相加,形成经过此节点的候选直径。
返回与副作用
向父返回一支;全局记录两支连接后的最大边数。
边界检查
返回深度按节点计,直径按边计,不能混淆单位。
迁移 · #124 二叉树中的最大路径和最大路径和把长度换成权值和,负增益分支可舍弃。
先说清契约,再独立实现进入本单元 ↗
UNIT 07单支返回、双支更新

#124 二叉树中的最大路径和

子问题契约
返回父节点可以继续连接的最大单支增益。
分解与合并
用非负的左右增益更新经过当前节点的路径,再选择一侧返回。
返回与副作用
局部返回只能连一支,全局答案可连两支。
边界检查
答案初始化须覆盖全负数树,路径至少含一个节点。
迁移 · #687 最长同值路径同值路径把分支可用性改为父子值相同,仍区分单支和双支。
先说清契约,再独立实现进入本单元 ↗
UNIT 08路径历史与恢复

#437 路径总和 III

子问题契约
当前根路径的前缀和,以及活动祖先前缀频次。
分解与合并
先查询需要的祖先前缀,再加入当前前缀,离开时撤销。
返回与副作用
每个孩子只看到自己的祖先历史,不包含兄弟分支。
边界检查
虚拟零前缀、重复前缀及负节点值都须保留正确计数。
迁移 · #560 和为 K 的子数组子数组和只有一条线性历史,不需要退出分支时的撤销。
先说清契约,再独立实现进入本单元 ↗
1. 节点就是子问题
2. 函数契约
3. Base Case
4. 调用栈
5. 返回值汇聚
6. Python与迁移
二叉树与当前子问题点击节点观察完整子树
当前函数契约尚未定义:depth(node)返回什么?

真实调用栈(顶部为当前函数)

尚未开始
当前node1
当前阶段观察
left_depth—
right_depth—
已完成返回值
暂无

递归不变量

每次调用depth(node)都只承诺正确返回当前子树最大深度。

当前层责任边界

当前层无需知道子树内部过程,只使用左右递归调用的返回值。

当前节点1
栈深度0
完成返回0
当前返回值—
根节点答案—
补全递归函数显式左右返回值
def depth(node):
if :
return
left_depth =
right_depth =
return
def depth(node): if node is None: return 0 left_depth = depth(node.left) right_depth = depth(node.right) return 1 + max(left_depth, right_depth)
为什么不用一行写法?拆成left_depth和right_depth后,父调用暂停、两个子调用返回、父调用恢复的过程才真正可见。
代码—树—栈—返回值联动示例树深度4
1def depth(node):
2 if node is None:
3 return 0
5 left_depth = depth(node.left)
6 right_depth = depth(node.right)
8 return 1 + max(left_depth, right_depth)
播放后观察:父调用会停在某一行等待子调用返回。
完成代码联动后进入迁移练习代码正确、查看答案、一键填入或播放完整轨迹后解锁。

迁移练习:把“最大深度”改成“节点总数”

count(node)返回以node为根的子树节点总数。递归结构保持不变,但契约和组合规则改变。

第5章 Unit 00 Skill卡

树递归的核心是:先定义返回值契约,再让Base Case提供最小结构答案,让左右子树按同一契约返回,最后由当前节点组合结果。

触发信号问题可定义在“以当前节点为根的子树”上
核心表示node参数+返回值契约
不变量每次调用都按同一契约返回当前子树答案
核心操作左递归、右递归、当前节点组合
常见错误未定义返回值;混淆当前层与子树内部责任
推荐下一题LeetCode #104 二叉树的最大深度