L2-3 自然倍树
一、题目概述
自然倍树 是一种特殊的二叉树,其核心性质为:第 iii 层结点的键值都必须是 iii 的倍数 (根结点为第 1 层)。

关键挑战
本题融合了三大经典二叉树算法:
- 后序遍历 + 中序遍历 → 重建二叉树
- 层序遍历(BFS)→ 按层获取结点,确定深度
- 倍数判定 → 验证自然倍树性质
二、核心思路
2.1 算法流程图
输入后序 + 中序序列
↓
重建二叉树(递归分治,pop后序末尾作为根)
↓
层序遍历,按层收集结点值
↓
遍历每层:检查 value % (层数) == 0 ?
↓
全部满足 → 输出 1,否则输出 0
2.2 重建二叉树的原理
| 遍历方式 | 特性 | 作用 |
|---|---|---|
| 后序遍历 | 最后一个元素是根结点 | 确定当前子树的根 |
| 中序遍历 | 根结点左侧是左子树,右侧是右子树 | 划分左右子树范围 |
递归公式:
- 后序序列:
[左子树后序] [右子树后序] [根] - 中序序列:
[左子树中序] [根] [右子树中序]
Python 技巧: 利用
list.pop()从末尾弹出根,天然契合后序遍历"根在最后"的特性。
三、代码实现详解
3.1 二叉树结点类
python
class TreeNode:
def __init__(self, val=0):
self.val = val # 结点键值
self.left = None # 左孩子
self.right = None # 右孩子
3.2 重建二叉树(核心函数)
python
def build(post, ino):
"""
根据后序和中序遍历序列重建二叉树
:param post: 后序遍历列表(会被修改,pop末尾作为根)
:param ino: 中序遍历列表
:return: 重建后的根结点
"""
if not ino: # 递归边界:中序为空,空子树
return None
root_val = post.pop() # 后序最后一个 = 当前子树根
idx = ino.index(root_val) # 在中序中定位根的位置
root = TreeNode(root_val)
# ⚠️ 关键:先构建右子树,再构建左子树!
# 因为 post.pop() 是从后往前取根,右子树的根更靠近末尾
root.right = build(post, ino[idx + 1:]) # 右子树中序
root.left = build(post, ino[:idx]) # 左子树中序
return root
为什么先 right 后 left?
后序序列:[左子树...] [右子树...] [根]
↑从前弹出 ↑需要先构建 ↑最后pop()
pop顺序:根 → 右子树根 → 左子树根(逆序)
所以必须 先右后左,才能匹配 pop 的顺序!
3.3 层序遍历(BFS,按层收集)
python
from collections import deque
def level(root):
"""
层序遍历,按层返回结点值
:param root: 二叉树根结点
:return: 二维列表,每个子列表是一层的结点值
"""
if not root:
return []
res = []
q = deque([root])
while q:
level_size = len(q) # 当前层的结点数量
cur_level = []
for _ in range(level_size):
node = q.popleft()
cur_level.append(node.val)
if node.left:
q.append(node.left)
if node.right:
q.append(node.right)
res.append(cur_level) # 一层收集完毕
return res
返回示例: [[13], [10, 18], [6, 9], [20]] --- 每层一个子列表。
3.4 主程序:判定自然倍树
python
m = int(input()) # 测试数据组数 (m ≤ 20)
for _ in range(m):
n = int(input()) # 结点数 (n ≤ 30)
post = list(map(int, input().split())) # 后序遍历
ino = list(map(int, input().split())) # 中序遍历
root = build(post, ino) # 重建二叉树
levels = level(root) # 层序遍历结果
ok = True
for idx in range(len(levels)): # idx = 0,1,2... 对应第 1,2,3...层
for v in levels[idx]:
if v % (idx + 1) != 0: # 第 idx+1 层,必须是 idx+1 的倍数
ok = False
break
if not ok:
break
print(1 if ok else 0)
四、关键要点与易错点
4.1 重建时 必须先右后左(极易出错!)
| 错误顺序 | 结果 | 原因 |
|---|---|---|
left → right |
❌ 错误 | pop() 先拿到右子树根,却被拿去建左子树 |
right → left |
✅ 正确 | 与 post.pop() 的逆序弹出保持一致 |
图示理解:
后序: [左左左] [右右] [根]
↑pop顺序: 根 → 右 → 左
build执行:
1. pop() 得 根
2. 需要右子树的根 → 再次pop() 正好是右子树的根!
3. 所以先递归 build(right)
4.2 层数与索引的对应
python
levels = [[13], [10, 18], [6, 9], [20]]
# idx=0 idx=1 idx=2 idx=3
# 第1层 第2层 第3层 第4层
# 判定条件:v % (idx + 1) == 0
索引 idx |
实际层数 | 判定条件 |
|---|---|---|
| 0 | 第 1 层 | v % 1 == 0 |
| 1 | 第 2 层 | v % 2 == 0 |
| 2 | 第 3 层 | v % 3 == 0 |
| ... | ... | ... |
⚠️ 注意: 题目定义根为第 1 层,不是第 0 层!
4.3 样例验证
样例 1(图 1,应输出 1):
后序:10 20 6 9 18 13
中序:10 13 20 6 18 9
层序结果:[[13], [10, 18], [6, 9], [20]]
检查:
第1层: 13 % 1 = 0 ✓
第2层: 10 % 2 = 0 ✓, 18 % 2 = 0 ✓
第3层: 6 % 3 = 0 ✓, 9 % 3 = 0 ✓
第4层: 20 % 4 = 0 ✓
→ 输出 1
样例 2(图 2,应输出 0):
后序:6 9 10 20 18 13
中序:6 13 9 10 18 20
层序结果:[[13], [6, 18], [10, 20], [9]]
检查:
第1层: 13 % 1 = 0 ✓
第2层: 6 % 2 = 0 ✓, 18 % 2 = 0 ✓
第3层: 10 % 3 = 1 ✗ ← 不满足!
→ 输出 0
五、复杂度分析
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n2)O(n^2)O(n2) | ino.index() 每次 O(n)O(n)O(n),共 nnn 次;或用 dict 预存索引优化到 O(n)O(n)O(n) |
| 空间复杂度 | O(n)O(n)O(n) | 递归栈深度 + 队列空间 + 切片产生的临时列表 |
六、算法拓展与变式
6.1 切片 vs 索引优化
当前代码使用列表切片 ino[:idx] 和 ino[idx+1:],简洁但会创建新列表。
优化版本(避免切片,传递索引边界):
python
def build(post, ino, in_left, in_right):
if in_left > in_right:
return None
root_val = post.pop()
idx = ino.index(root_val) # 可预存为 dict 优化到 O(1)
root = TreeNode(root_val)
root.right = build(post, ino, idx + 1, in_right)
root.left = build(post, ino, in_left, idx - 1)
return root
6.2 类似题型对比
| 题型 | 核心操作 | Python 技巧 |
|---|---|---|
| 前序+中序重建 | 前序第一个为根 | pop(0) 或维护索引指针 |
| 后序+中序重建 | 本题,后序最后一个为根 | pop() 最优雅 |
| 层序+中序重建 | 用队列辅助,按层确定根 | 队列 deque |
6.3 本题变式思考
- 变式 1: 若要求第 iii 层结点是 iii 的约数 ?→ 改
v % (idx+1) != 0为(idx+1) % v != 0 - 变式 2: 若键值范围很大(如 10910^9109)?→ 无需修改,取模运算不受影响
- 变式 3: 若结点值不唯一?→ 题目已保证不重复,否则
index()会失效
七、总结口诀
后序 pop 尾,必然是树根;
中序分左右,先右后左建;
层序走 BFS,按层收集全;
索引加一为层数,取模判倍数,全过才是 1。
八、完整 AC 代码
python
from collections import deque
class TreeNode:
def __init__(self, val=0):
self.val = val
self.left = None
self.right = None
def build(post, ino):
"""后序+中序重建二叉树(先右后左!)"""
if not ino:
return None
root_val = post.pop()
idx = ino.index(root_val)
root = TreeNode(root_val)
root.right = build(post, ino[idx + 1:]) # 先右!
root.left = build(post, ino[:idx]) # 后左!
return root
def level(root):
"""层序遍历,按层返回结点值"""
if not root:
return []
res, q = [], deque([root])
while q:
level_size = len(q)
cur_level = []
for _ in range(level_size):
n = q.popleft()
cur_level.append(n.val)
if n.left:
q.append(n.left)
if n.right:
q.append(n.right)
res.append(cur_level)
return res
m = int(input())
for _ in range(m):
n = int(input())
post = list(map(int, input().split()))
ino = list(map(int, input().split()))
root = build(post, ino)
levels = level(root)
ok = True
for idx in range(len(levels)):
for v in levels[idx]:
if v % (idx + 1) != 0:
ok = False
break
if not ok:
break
print(1 if ok else 0)
题目来源: PAT (Advanced Level) / 天梯赛 L2-3
难度评级: ⭐⭐⭐(中等,综合性强)
核心考点: 二叉树重建 + 层序遍历 + 性质判定