每日一题——自然倍树

L2-3 自然倍树

一、题目概述

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

关键挑战

本题融合了三大经典二叉树算法:

  1. 后序遍历 + 中序遍历 → 重建二叉树
  2. 层序遍历(BFS)→ 按层获取结点,确定深度
  3. 倍数判定 → 验证自然倍树性质

二、核心思路

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
为什么先 rightleft
复制代码
后序序列:[左子树...] [右子树...] [根]
           ↑从前弹出   ↑需要先构建   ↑最后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 重建时 必须先右后左(极易出错!)

错误顺序 结果 原因
leftright ❌ 错误 pop() 先拿到右子树根,却被拿去建左子树
rightleft ✅ 正确 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
难度评级: ⭐⭐⭐(中等,综合性强)
核心考点: 二叉树重建 + 层序遍历 + 性质判定

相关推荐
knight_9___2 小时前
RAG面试题4
开发语言·人工智能·python·面试·agent·rag
源码之家2 小时前
计算机毕业设计:Python股票数据可视化与LSTM股价预测系统 Flask框架 LSTM Keras 数据分析 可视化 深度学习 大数据 爬虫(建议收藏)✅
大数据·python·深度学习·信息可视化·django·lstm·课程设计
BU摆烂会噶2 小时前
【LangGraph】实战:基于 LangGraph 实现的智能文档问答系统
人工智能·python·langchain
噜噜噜阿鲁~2 小时前
python学习笔记 | 6.3、函数-函数的参数
笔记·python·学习
水木流年追梦2 小时前
CodeTop Top 300 热门题目3-字符串相加
java·前端·算法
Lazionr2 小时前
【链表经典OJ-中】
c语言·数据结构·链表
2301_813599552 小时前
持久化存储如何适配不同浏览器?解决隐私模式下存储失败的指南
jvm·数据库·python
2501_914245932 小时前
SQL如何高效提取大表前几行:分页查询与OFFSET优化
jvm·数据库·python
一江寒逸2 小时前
数据结构与算法之美:绪论——构建算法思维的基石
数据结构·算法