01 / 本课学习路线
本课学习路线
阅读与推演约 110 分钟,练习约 55 分钟,进阶练习另需约 20 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
树上的递归只有三个设计决定:返回值是什么、参数带什么、答案在哪一层更新。写代码前把这三句话写在注释里。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 从返回值、参数、答案更新位置三个方面描述树题的递归设计 | 第 07 节 | 自查第 2 条、代码自测 |
| 聚合走返回值、路径走参数的分工能举例说明 | 第 04–06 节 | 自查第 3 条、练习 4、5 |
| 三种输入形式(父亲数组 / 边表 / 数组式)都会还原成孩子表 | 第 07 节 | 自查第 4 条 |
| Python 深树的递归深度设置已成为开始编码前的固定检查步骤 | 第 07、09 节 | 自查第 5 条 |
| 独立通过 P3514、P3904 | 第 08 节 | 必做任务 1、2 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成模块 3 的「深度优先搜索与回溯」。
展开先修自测答案
自测 1:left_sum = dfs(left)——先递归拿到孩子的结果,再把它加进自己的结果里返回。这就是「聚合走返回值」。
自测 2:nonlocal best(模块级变量则 global best)。本课参考程序里 best 在模块级,用 global。
自测 3:前者每个元素是独立的空列表;后者 n + 1 个名字指向同一个列表,往一个里 append 全都变。孩子表必须用前者。
自测 4:2i + 1 与 2i + 2;下标 5 的孩子是 11 和 12。P3904 题面示例里下标 5(值 15)的孩子正是下标 11(3)、12(2)。
自测 5:约 1000。链状树递归会抛 RecursionError;开头写 sys.setrecursionlimit(300000),或改用显式栈。
03 / 概念与术语
根、孩子表、叶子、聚合方向、路径方向
树是无环连通图:从根出发、只往孩子走,每个节点恰好访问一次,不需要 visited 标记(边表给的无向边除外——那时带上父节点参数、不回头)。
| 术语 | 含义 | 代码里的位置 |
|---|---|---|
| 根 | 没有父节点的那个节点;不一定是 1 号 | root = 不是任何人孩子的节点 |
| 孩子表 | children[u] = u 的直接孩子列表 | 读入边时 children[p].append(c) |
| 叶子 | 没有孩子的节点;路径类问题在这里更新答案 | if not has_l and not has_r |
| 聚合方向(向上) | 子树和、子树大小这类「由孩子算出父亲」的量,用返回值传 | return left_sum + right_sum + val |
| 路径方向(向下) | 根到当前的累计耗时这类「由父亲传给孩子」的量,用参数传 | dfs(child, acc + val) |
| 答案更新点 | 每个节点 / 只在叶子 / 只在根:由题意决定 | best = max(best, ...) 放的位置 |
补充学习(选学)P3608 二叉树计算:返回值设计练习约 6 分钟「新值 = 左子树和 + 右子树和」的返回值应该是什么
新树每个节点的值 = 原树该节点左子树所有节点值之和 + 右子树所有节点值之和。递归函数(dfs)应返回「以当前节点为根的原树子树节点值之和」:左子树和(left_sum)= dfs(左),右子树和(right_sum)= dfs(右),当前节点的新值 = left_sum + right_sum,向上返回 left_sum + right_sum + 当前节点的原值。明确返回值后,一次后序遍历即可同时算出新值和子树和。本题还要先由前序 + 中序还原二叉树:前序第一个是根,在中序里找到根,左边是左子树、右边是右子树,递归两半——第 06 节逐节点列出。
04 / P3514 小家庭
聚合只到直接孩子;根不一定是 1 号
P3514「寻找最富裕的小家庭」:第一行 N(≤ 1000),第二行 N 个财富值(编号 1..N),随后 N−1 行「N1 N2」表示 N1 是 N2 的父节点;一个节点及其直接子节点是一个小家庭,输出财富和最大的小家庭。题面没有给示例,下面的数字是本课自己算的。
| 节点 | 财富 | 直接孩子 | 小家庭和 |
|---|---|---|---|
| 1 | 10 | 2、3 | 10 + 5 + 8 = 23 |
| 2 | 5 | 4、5 | 5 + 1 + 2 = 8 |
| 3 | 8 | 无 | 8 |
| 4 | 1 | 无 | 1 |
| 5 | 2 | 无 | 2 |
输出 23。孙辈 4、5 不算进节点 1 的家庭;若误读成「整棵子树求和」,节点 1 会算成 26。没有孩子的节点按题目页参考题解也算一个家庭(和就是自己),本例不影响答案。
根不一定是 1 号:3 / 5 6 7 / 2 1 / 2 3
边 2→1、2→3:节点 1 和 3 都是孩子,只有 2 不是任何人的孩子 → 根是 2 家庭(2) = 6 + 5 + 7 = 18;家庭(1) = 5;家庭(3) = 7 → 输出 18 假定根是 1 号:从 1 出发看不到 2、3,只得 5
三个问题:返回值——不需要(每个节点当场算自己的小家庭);参数——不需要;答案更新点——每个节点。递归只用来「走到每个节点」,用显式栈或队列遍历也行。
05 / P3904 悄悄话
累计耗时作为参数下传,叶子处更新最晚时刻;数组式二叉树的还原
P3904「悄悄话花费的时间」:一行数组表示二叉树(下标 i 的孩子是 2i+1、2i+2,−1 是空节点),节点数字是父节点向它传话的耗时,根是消息源;输出所有人都收到消息的时刻。题面示例:0 9 20 -1 -1 15 7 -1 -1 -1 -1 3 2 → 38。
| 下标 | 值 | 左孩子 (2i+1) | 右孩子 (2i+2) |
|---|---|---|---|
| 0 | 0(根) | 1 (9) | 2 (20) |
| 1 | 9 | 3 (−1,空) | 4 (−1,空) |
| 2 | 20 | 5 (15) | 6 (7) |
| 5 | 15 | 11 (3) | 12 (2) |
| 6 | 7 | 13(越界) | 14(越界) |
| 11 | 3 | 越界 | 越界 |
| 12 | 2 | 越界 | 越界 |
空节点 −1 只占一个下标、不是真实节点;它的位置仍然要留着,否则后面的下标全错。叶子是 1、6、11、12。
| 叶子 | 路径 | 累计 |
|---|---|---|
| 1 | 0 → 9 | 9 |
| 6 | 0 → 20 → 7 | 27 |
| 11 | 0 → 20 → 15 → 3 | 38 |
| 12 | 0 → 20 → 15 → 2 | 37 |
答案是最晚收到消息的叶子:38。根自己的值是 0(消息源),acc 从 0 起。三个问题:返回值——不需要;参数——累计耗时;答案更新点——叶子。
再算一组:0 2 3 4
下标 1(值 2)的孩子是 3(值 4)和 4(越界);下标 2(值 3)无孩子 叶子 3:0 + 2 + 4 = 6;叶子 2:0 + 3 = 3 → 输出 6
06 / P3608 二叉树计算
由前序 + 中序还原,返回值是子树和,中序写出新值
P3608「二叉树计算」(进阶):两行输入分别是二叉树的中序遍历与前序遍历(节点值互不相同);新树每个节点 = 原树该节点左子树和 + 右子树和;输出新树的中序遍历。题面示例:中序 8 12 -3 6 -10 9 -7、前序 -3 12 8 6 9 -10 -7 → 0 8 18 -8 0 -17 0。
由前序 + 中序还原:前序第一个是根,在中序里切两半
前序 -3 12 8 6 9 -10 -7 → 根 -3;中序里 -3 在第 2 位 → 左子树中序 [8 12]、右子树中序 [6 -10 9 -7] 左子树:前序 [12 8] → 根 12;中序 [8 12] → 12 的左孩子 8,没有右孩子 右子树:前序 [6 9 -10 -7] → 根 6;中序 [6 -10 9 -7] → 6 没有左孩子,右子树中序 [-10 9 -7]、前序 [9 -10 -7] → 根 9,左 -10、右 -7
| 节点 | 左孩子 | 右孩子 | 左子树和 | 右子树和 | 新值 | 向上返回 |
|---|---|---|---|---|---|---|
| 8 | — | — | 0 | 0 | 0 | 8 |
| 12 | 8 | — | 8 | 0 | 8 | 8 + 0 + 12 = 20 |
| -3(根) | 12 | 6 | 20 | -2 | 18 | 20 − 2 − 3 = 15 |
| 6 | — | 9 | 0 | -8 | -8 | 0 − 8 + 6 = -2 |
| -10 | — | — | 0 | 0 | 0 | -10 |
| 9 | -10 | -7 | -10 | -7 | -17 | -10 − 7 + 9 = -8 |
| -7 | — | — | 0 | 0 | 0 | -7 |
新树中序 0 8 18 -8 0 -17 0,与题面一致。「新值」用的是左右子树在原树里的和,不含节点自己;「向上返回」才把自己加进去——两者分开,父节点才能拿到正确的子树和。
三个问题:返回值——子树在原树里的和;参数——前序起点与中序区间;答案更新点——每个节点(按中序位置写进输出列表)。写出「新值」的时机在左子树递归之后、右子树递归之前,输出顺序自然就是中序。
07 / 分工、输入形式与递归深度
返回值与参数的分工表;三种输入形式;链状树的递归深度
每一项信息只选一种传递方式:聚合的走返回值,路径的走参数;两个方向可以同时用。
| 题目 | 返回值 | 参数 | 答案更新点 |
|---|---|---|---|
| P3514 小家庭 | 不需要 | 不需要 | 每个节点 |
| P3904 悄悄话 | 不需要 | 累计耗时 acc | 叶子 |
| P3608 二叉树计算 | 子树和 | 前序起点、中序区间 | 每个节点(中序位置) |
| 题目的输入形式 | 还原动作 | 注意 |
|---|---|---|
| 父子对 (父, 子) | 扫一遍建 children[父].append(子) | 找根:不是任何人孩子的那个(P3514) |
| 边表 (u, v),无向 | 两个端点互相加入邻接表;DFS 时跳过父节点 | 从根出发带 parent 参数,避免回到父节点 |
| 数组式二叉树 | 下标 i 的孩子 2i+1 / 2i+2 | 空节点标记(如 −1)不是真实节点,但位置要留(P3904) |
| 前序 + 中序 | 前序第一个是根,在中序里切左右两半,递归 | 节点值互不相同才能用值定位(P3608) |
先完成输入解析并构建孩子表,再执行递归,这样可以分别验证数据结构和递归逻辑。
链状树会超过 Python 的默认递归深度(约 1000)
开头写 sys.setrecursionlimit(300000),或改用显式栈迭代。这是语言层面的易错点,不是算法问题:1500 个节点的链状树(超出题目 N ≤ 1000,只用来演示语言限制),不调整递归深度会直接抛出 RecursionError(第 09 节错误表)。
树递归代码框架:返回值与参数各司其职
Pythonimport sys
def main() -> None:
sys.setrecursionlimit(300000)
data = sys.stdin.read().split()
# 按题目要求还原:children[u] = [...] 与节点值 val[u]
best = 0
def dfs(u: int, acc: int) -> int:
nonlocal best
# 待完成:先确定两类信息各自的传递方式——
# 聚合信息(如子树和)通过【返回值】向上传给父节点
# 路径信息(根到当前节点的累计值)通过【参数 acc】向下传给子节点
...
dfs(root, 0)
print(best)
main()每个节点访问一次 O(n);空间 O(h),h 是树高——链状树 h=n,这就是需要处理递归深度的原因。
08 / 从三个问题到程序
三道题的完整参考程序,每一步落在哪几行
先自己写完并提交一次,再展开对照。
| 步骤 | P3514 | P3904 | P3608 |
|---|---|---|---|
| 还原 | children[p].append(c),找根 | 不建表:按下标算孩子 | pos 字典 + 递归切区间 |
| 递归入口 | dfs(root) | dfs(0, 0) | build(0, 0, n - 1) |
| 聚合 / 路径 | family = val[u] + Σ val[c] | total = acc + nums[i] | return left_sum + right_sum + root_val |
| 答案 | 每节点 best = max | 叶子 best = max | 中序位置写 out[idx] |
展开完整参考程序 1:P3514 寻找最富裕的小家庭(先自己写完并提交一次,再展开对照)
完整程序:P3514(标准输入 → 标准输出)
Pythonimport sys
sys.setrecursionlimit(300000) # 链状树的递归深度可能接近 N
data = sys.stdin.read().split()
n = int(data[0])
val = [0] + [int(x) for x in data[1:1 + n]] # 编号 1..N
children = [[] for _ in range(n + 1)]
is_child = [False] * (n + 1)
for i in range(n - 1):
p, c = int(data[1 + n + 2 * i]), int(data[2 + n + 2 * i]) # p 是 c 的父节点
children[p].append(c)
is_child[c] = True
root = next(u for u in range(1, n + 1) if not is_child[u]) # 唯一不是任何人孩子的节点
best = 0
def dfs(u):
global best
family = val[u] # 小家庭 = 自己 + 直接孩子(孙辈不算)
for c in children[u]:
family += val[c]
dfs(c) # 继续往下:每个节点各自算一次自己的小家庭
if family > best:
best = family
dfs(root)
print(best)用第 04 节的五节点 → 23 和根是 2 号的 3 / 5 6 7 / 2 1 / 2 3 → 18 核对;链状树靠开头的递归上限设置才能跑完。思路与题目页参考题解(Java / C / Go)相同。
展开完整参考程序 2:P3904 悄悄话花费的时间
完整程序:P3904(标准输入 → 标准输出)
Pythonimport sys
sys.setrecursionlimit(300000)
nums = [int(x) for x in sys.stdin.read().split()] # 数组式二叉树:下标 i 的孩子是 2i+1、2i+2,-1 是空节点
n = len(nums)
best = 0
def dfs(i, acc): # acc = 根到 i 的父节点为止的累计耗时(参数向下传)
global best
total = acc + nums[i] # 传到 i 本人的时刻(根的值是 0)
l, r = 2 * i + 1, 2 * i + 2
has_l = l < n and nums[l] != -1
has_r = r < n and nums[r] != -1
if not has_l and not has_r: # 叶子:一条根到叶的路走完,更新最晚时刻
if total > best:
best = total
return
if has_l:
dfs(l, total)
if has_r:
dfs(r, total)
dfs(0, 0)
print(best)用题面示例 → 38 和 0 2 3 4 → 6 核对。
展开完整参考程序 3:P3608 二叉树计算(进阶练习)
完整程序:P3608(标准输入 → 标准输出)
Pythonimport sys
sys.setrecursionlimit(300000)
lines = sys.stdin.read().split("\n")
inorder = [int(x) for x in lines[0].split()]
preorder = [int(x) for x in lines[1].split()]
pos = {v: i for i, v in enumerate(inorder)} # 值 → 在中序里的位置(题目保证值互不相同)
out = [] # 新树的中序遍历
def build(pre_l, in_l, in_r): # 返回:这段子树在原树里的节点值之和;顺便按中序把新值写进 out
if in_l > in_r:
return 0
root_val = preorder[pre_l]
k = pos[root_val] # 根在中序里的位置:左边是左子树,右边是右子树
left_size = k - in_l
left_sum = build(pre_l + 1, in_l, k - 1) # 左子树:前序紧跟根,中序在根左边
out.append(left_sum + 0) # 先占位,下面补上右子树和
idx = len(out) - 1
right_sum = build(pre_l + 1 + left_size, k + 1, in_r)
out[idx] = left_sum + right_sum # 新值 = 左子树和 + 右子树和(中序位置:左子树之后、右子树之前)
return left_sum + right_sum + root_val # 向上返回整棵子树在原树里的和
build(0, 0, len(inorder) - 1)
print(" ".join(map(str, out)))用题面示例(中序 8 12 -3 6 -10 9 -7、前序 -3 12 8 6 9 -10 -7 → 0 8 18 -8 0 -17 0)核对。还原与求和在同一次递归里完成。
09 / 边界、反例与复杂度
错误做法在具体输入上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。
| 错误做法 | 输入 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|---|
| P3514 小家庭算成整棵子树 | 第 04 节五节点 | 26 | 23 | 答案错误(WA) |
| P3514 假定根是 1 号 | 3 / 5 6 7 / 2 1 / 2 3 | 5 | 18 | 答案错误(WA) |
| P3514 不设递归上限 | 1500 节点链状树(只演示语言限制) | 抛出 RecursionError | 2 | 运行错误(RE) |
| P3904 孩子下标写成 2i / 2i+1 | 题面示例 | 抛出 RecursionError(根 0 的左孩子被算成 0,自己调用自己) | 38 | 运行错误(RE) |
| P3904 把所有值加起来 | 题面示例 | 56 | 38 | 答案错误(WA) |
| P3608 新值加上了节点自己(写成子树和) | 题面示例 1 | 8 20 15 -2 -10 -8 -7 | 0 8 18 -8 0 -17 0 | 答案错误(WA) |
| P3608 按前序输出 | 题面示例 1 | 18 8 0 -8 -17 0 0 | 0 8 18 -8 0 -17 0 | 答案错误(WA) |
| P3608 两行输入读反(先前序后中序) | 题面示例 1 | 0 -3 7 -8 0 2 0 | 0 8 18 -8 0 -17 0 | 答案错误(WA) |
P3904 把 −1 当真实节点参与递归时,题面示例碰巧仍输出 38(−1 的孩子越界、且它的值不在任何叶子路径上),不能靠它发现错误:要用第 05 节的节点表逐个核对叶子。
| 做法 | 时间 | 空间 | 本课规模下 |
|---|---|---|---|
| 一次递归遍历 | O(n) | O(h) | N ≤ 1000 瞬间完成;链状树 h = n,需设递归上限 |
| 每个节点重算子树和 | O(n²) | O(h) | N = 1000 约 10⁶,仍可通过,但没必要 |
| 前序 + 中序还原(用字典定位根) | O(n) | O(n) | 每层切区间一次 |
10 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):按第 04 节表的格式,给 3 / 1 1 100 / 1 2 / 2 3 逐节点列小家庭和,写出答案。
展开练习 1 答案
家庭(1) = 1 + 1 = 2;家庭(2) = 1 + 100 = 101;家庭(3) = 100。答案 101——最富的小家庭在中间层,所以答案更新点必须是每个节点,不能只看根。
练习 2(改一个条件):P3514 的「小家庭」改成「整棵子树」,第 04 节五节点的答案变成多少?递归的返回值要怎么改?
展开练习 2 答案
26(节点 1 的整棵子树 10 + 5 + 8 + 1 + 2)。返回值从「不需要」变成「子树和」:family += dfs(c) 且函数末尾 return family——聚合的层级变了,信息就要沿返回值向上带。
练习 3(改一个条件):P3904 改成「每个节点收到消息后还要额外花 1 才能开始传给孩子」,题面示例的答案变成多少?
展开练习 3 答案
最晚的叶子 11 路径上有 3 个中间节点(0、20、15),各加 1 → 38 + 3 = 41;其它叶子 9 + 1 = 10、27 + 2 = 29、37 + 3 = 40。只改参数下传的式子:dfs(child, total + 1)。
练习 4(独立实现):把 P3514 写成函数 richest_family(n, val, edges),用第 04 节五节点、根不是 1 号、练习 1、单节点各写一条断言。
展开练习 4 答案
richest_family 的参考实现(自带断言)
Pythondef richest_family(n, val, edges):
# val[1..n]:财富;edges:(父, 子);小家庭 = 自己 + 直接孩子
children = [[] for _ in range(n + 1)]
is_child = [False] * (n + 1)
for p, c in edges:
children[p].append(c)
is_child[c] = True
root = next(u for u in range(1, n + 1) if not is_child[u])
best = 0
stack = [root] # 用显式栈遍历:每个节点各自算一次自己的小家庭
while stack:
u = stack.pop()
family = val[u] + sum(val[c] for c in children[u])
best = max(best, family)
stack.extend(children[u])
return best
V = [0, 10, 5, 8, 1, 2]
assert richest_family(5, V, [(1, 2), (1, 3), (2, 4), (2, 5)]) == 23 # 第 04 节:1 + 直接孩子 2、3
assert richest_family(3, [0, 5, 6, 7], [(2, 1), (2, 3)]) == 18 # 根不是 1 号:2 + 孩子 1、3
assert richest_family(3, [0, 1, 1, 100], [(1, 2), (2, 3)]) == 101 # 最富的小家庭在中间
assert richest_family(1, [0, 7], []) == 7 # 单节点用显式栈遍历,不受递归深度限制;每个节点当场算自己的小家庭。
练习 5(迁移):把 P3904 写成函数 whisper_time(nums),用题面示例、0 2 3 4、只有根、0 5 -1 4 各写一条断言。
展开练习 5 答案
whisper_time 的参考实现(自带断言)
Pythondef whisper_time(nums):
# 数组式二叉树:下标 i 的孩子是 2i+1、2i+2,-1 是空节点;节点值 = 父传子的耗时
n = len(nums)
best = 0
def dfs(i, acc): # acc:根到 i 的父节点为止的累计(参数向下传)
nonlocal best
total = acc + nums[i]
l, r = 2 * i + 1, 2 * i + 2
has_l = l < n and nums[l] != -1
has_r = r < n and nums[r] != -1
if not has_l and not has_r: # 叶子处更新
best = max(best, total)
return
if has_l:
dfs(l, total)
if has_r:
dfs(r, total)
dfs(0, 0)
return best
assert whisper_time([0, 9, 20, -1, -1, 15, 7, -1, -1, -1, -1, 3, 2]) == 38 # P3904 题面示例(第 05 节表)
assert whisper_time([0, 2, 3, 4]) == 6 # 第 05 节小例
assert whisper_time([0]) == 0 # 只有根
assert whisper_time([0, 5, -1, 4]) == 9 # 右空、左链最后一条:右孩子是空节点、左孩子 5 的左孩子是 4 → 0 + 5 + 4 = 9。
11 / 读题要求与复习自评
三道题的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | P3514 小家庭 | P3904 悄悄话 | P3608 二叉树计算 |
|---|---|---|---|
| 输入形式 | N;财富行;N−1 行「父 子」 | 一行数组式二叉树,−1 为空 | 两行:中序、前序 |
| 输出 | 最大小家庭和 | 最晚到达时刻 | 新树的中序遍历 |
| 返回值 / 参数 | 无 / 无 | 无 / 累计耗时 | 子树和 / 区间 |
| 答案更新点 | 每个节点 | 叶子 | 每个节点(中序位置) |
| 示例 | 本课自算 → 23 | 题面 → 38 | 题面 → 0 8 18 -8 0 -17 0 |
需要对照解法时,展开本页第 08 节的三份完整参考程序。复习与自评:本课的「学习完成检查」六条是自评,不改变题目的通过(AC)状态,本课算完成的条件以页面下方「学习完成检查」处的说明为准。复习时用三个问题自测:① 不看正文,说出三道题各自的返回值、参数、答案更新点;② 不看表格,把题面示例的数组还原成树并算出四个叶子的累计;③ 说出链状树为什么会 RecursionError、怎么处理。答不出哪一条,就回到对应的节重读,再做第 10 节对应的练习。
12 / 练习
按顺序完成本课的任务
必做题已通过 0/2 道;进阶练习已通过 0/1 道
对两道必做题各回答递归设计的三个问题
代码自测自主练习练习重点:返回值 / 参数 / 答案更新点,各用一句话写下来;预计用时:10 分钟
完成标准:两题的三问都能清楚说出
需要时查看提示
P3514:返回值可以不需要(遍历时当场加直接孩子并更新最大值),参数不需要,答案在每个节点更新。P3904:返回值不需要,参数带累计耗时(acc),答案在叶子更新。第 07 节有三道题的分工表。
P3514 · 寻找最富裕的小家庭
必做任务 1练习重点:「直接子节点」的聚合层级;孩子表还原;找根;预计用时:20 分钟
完成标准:能解释为什么家庭(1) 是 23 不是 26
需要时查看提示
小家庭 = 自己 + 直接孩子,孙辈不算。输入的每行「N1 N2」是 N1 为 N2 的父节点;根是不是任何人孩子的那个节点,不一定是 1 号。还原孩子表后遍历每个节点,当场用节点值数组(val)计算 val[u] + Σ val[孩子],与最大值比较并更新即可。第 04 节有逐节点表。
P3904 · 悄悄话花费的时间
必做任务 2练习重点:边权累计作为参数下传;数组式二叉树的空节点标记;预计用时:25 分钟
完成标准:能说出答案为什么在叶子处更新、根的初始 acc 是多少
需要时查看提示
节点值是「父传子的耗时」,根自己是消息源、acc 从 0 起。数组式二叉树下标 i 的孩子是 2i+1、2i+2,空节点标记 −1 不是真实节点——遇到就当没有这个孩子,位置仍要留着。第 05 节有节点表与累计表。
P3608 · 二叉树计算
进阶练习 1进阶练习练习重点:由前序 + 中序还原;一次后序遍历同时算新值与子树和;预计用时:20 分钟
完成标准:递归函数的返回值能用一句话说清
需要时查看提示
先看补充学习块想清返回值,再写。第一行是中序、第二行是前序;输出新树的中序,空格分隔。第 06 节有逐节点表。
提交结果
提交结果说明与处理方法
- WA
答案错误
常见错误:「直接子节点」读成全后代、假定根是 1 号、空节点标记当真实节点、新值多加了节点自己、输出顺序错。第 09 节的表给出了每种错误的具体输出
- PE
格式错误
输出树的题按题目要求的遍历序打印,分隔符逐项核对
- RE
运行错误
RecursionError = 忘了设置递归深度上限;下标越界 = 数组式二叉树的孩子下标算错
- TLE
超时
在递归里每次重算子树和是 O(n²)——聚合值要沿返回值带上来,只算一遍
- AC
通过
再测两组边界:单节点树、链状树(顺便验证你的递归深度设置真的生效)
13 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。