AlgoNote 题解:LeetCode 0572「另一棵树的子树」——递归匹配、序列化与哈希三种解法全解析
教程文档知识库【免费下载链接】AlgoNote⛽️「算法通关手册」从零开始的「算法与数据结构」学习教程200 道「算法面试热门题目」1000 道「LeetCode 题目解析」持续更新中项目地址https://gitcode.com/gh_mirrors/le/AlgoNote点击查看免费下载导读「另一棵树的子树」Subtree of Another Tree是 LeetCode 上一道经典的二叉树「树形同构判定」题目给定两棵二叉树root与subRoot判断subRoot是否为root的子树。该题同时出现在「算法面试 200 题」与「分类刷题清单」中见 docs/00_preface/00_06_categories_list.md 与 docs/00_preface/00_08_interview_200_list.md是面试中出现频率极高的递归入门题。读完本文你将掌握「树相等性判定」「子树包含判定」的递归分解方法理解 $O(m \times n)$ 复杂度的来源并学会序列化 字符串匹配KMP与子树哈希两种进阶优化思路。一、题目回顾什么是「另一棵树的子树」1.1 题目要求给定两棵二叉树的根节点root和subRoot要求检验root中是否包含和subRoot具有相同结构和节点值的子树。如果存在返回true否则返回false。这里对「子树」的定义非常严格来自 docs/05_tree/05_01_tree_basic.md 中树的递归定义一棵树的子树是该树中某个节点及其全部后代节点构成的树。也就是说subRoot必须与root中某一棵子树在结构上完全一致、对应节点值完全相同才算是匹配成功。1.2 示例解析示例 1输入root [3,4,5,1,2], subRoot [4,1,2] 输出trueroot中节点4及其后代1、2构成的结构与subRoot[4,1,2]完全相同因此返回true。示例 2输入root [3,4,5,1,2,null,null,null,null,0], subRoot [4,1,2] 输出false示例 2 与示例 1 仅差一处root的节点1多了一个值为0的左孩子。此时以节点4为根的子树变成了[4,1,2,null,0]结构与subRoot [4,1,2]不再一致因此返回false。这个对比直观地说明「子树」判定是「结构 值」的双重匹配只比较根节点值或只比较部分结构都是不够的。1.3 数据范围约束root树上节点数量范围[1, 2000]subRoot树上节点数量范围[1, 1000]节点值范围-10^4 ≤ root.val, subRoot.val ≤ 10^4约束决定了算法需要支持的最坏规模当root有 2000 个节点、subRoot有 1000 个节点时朴素递归的最坏比较次数约为 2000 × 1000 次量级仍然在可接受范围内这也是递归匹配能作为官方标准解的原因。二、核心概念铺垫树的相等性与子树2.1 二叉树的递归结构二叉树是一种递归定义的数据结构一个根节点 两棵互不相交的左、右子树左右子树本身也都是二叉树见 docs/05_tree/05_01_tree_basic.md。这一定义决定了凡是涉及「两棵树比较」的问题天然适合用递归分解到「比较根节点 递归比较左子树 递归比较右子树」。二叉树的链式存储节点定义如下同样来自 docs/05_tree/05_01_tree_basic.mdclass TreeNode: def __init__(self, val0, leftNone, rightNone): self.val val # 节点的值 self.left left # 左子节点指针 self.right right # 右子节点指针2.2 「相同的树」是子问题的基础判断子树是否匹配本质上是在判断以root中某个节点为根的树与subRoot是否完全相同。而「两棵树完全相同」的判定正是 LeetCode 0100「相同的树」这一基础题题解见 docs/solutions/0100-0199/same-tree.md两棵树相同当且仅当两棵树的根节点值相等且它们的左子树相同、右子树相同。其递归判定可以写成def isSameTree(p, q): if not p and not q: return True if not p or not q: return False if p.val ! q.val: return False return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)「相同的树」判定的时间复杂度为 $O(\min(m, n))$它正是「另一棵树的子树」这道题最核心的构件外层枚举root的每一个节点内层用「相同的树」逻辑做逐树比对。三、解法一递归匹配双重 DFS这是关联文档给出的标准解法也是所有后续优化方案的基础。3.1 思路拆解题目要求检查root中是否存在一棵与subRoot完全相同的子树。自然想到的策略是遍历root中的每一个节点这一步是外层 DFS对每个节点判断「以该节点为根的整棵子树」是否与subRoot完全相同这一步是内层 DFS即isSameTree。为此定义两个递归函数isSameTree(p, q)判断两棵树p和q是否完全相同结构与节点值均一致isSubtree(root, subRoot)判断subRoot是否是root的子树。对于isSubtree的递归逻辑递归终止条件如果当前root为空返回False空树不可能包含非空的subRoot本层判断检查以当前root为根的树是否与subRoot相同递归向下继续在root.left和root.right中寻找只要任意一侧包含subRoot整体结果即为True。3.2 完整代码# Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution: def isSubtree(self, root: Optional[TreeNode], subRoot: Optional[TreeNode]) - bool: # 判断两棵树是否完全相同 def isSameTree(p: Optional[TreeNode], q: Optional[TreeNode]) - bool: if not p and not q: return True if not p or not q: return False return p.val q.val and isSameTree(p.left, q.left) and isSameTree(p.right, q.right) # 如果 root 为空返回 False if not root: return False # 检查当前节点为根的树是否与 subRoot 相同 # 或者递归检查左右子树 return isSameTree(root, subRoot) or \ self.isSubtree(root.left, subRoot) or \ self.isSubtree(root.right, subRoot)3.3 递归执行过程以示例 1root [3,4,5,1,2]subRoot [4,1,2]为例递归过程如下isSubtree(root3, subRoot)isSameTree(3, 4)失败3 ! 4转向isSubtree(root.left4, subRoot)isSubtree(root4, subRoot)isSameTree(4, 4)两棵树的根节点值相等递归比较左子树isSameTree(1, 1)为True、右子树isSameTree(2, 2)为True因此整体返回True最终结果True。可以看出isSubtree是前序遍历式的外层 DFS先比较当前节点再递归左、右子树与 docs/05_tree/05_02_binary_tree_traverse.md 中二叉树前序遍历的递归模式完全一致。3.4 复杂度分析时间复杂度$O(m \times n)$其中 $m$ 是root的节点数$n$ 是subRoot的节点数。最坏情况下如root退化为一条链且每个节点都要完整比对一次subRoot外层需要检查root中的每个节点每次isSameTree比较需要 $O(n)$ 时间空间复杂度$O(m)$最坏情况下递归调用栈的深度为 $m$对应root退化为链的形态。需要说明的是这里的 $O(m \times n)$ 是最坏情况上界。当两棵树形态差异明显时例如根节点值不同isSameTree会在常数时间内提前返回实际运行远快于该上界。关于大 O 记号与最坏/平均复杂度分析的约定可参考 docs/00_preface/00_03_algorithm_complexity.md。四、解法二序列化 字符串匹配KMP 优化题目标签中包含「字符串匹配、哈希函数」暗示了更高级的思路把树结构问题转化为字符串匹配问题。4.1 思路拆解如果能把一棵二叉树「序列化」成一个唯一的字符串那么判断subRoot是否为root的子树就等价于判断subRoot的序列化串是否为root的序列化串的子串。具体步骤序列化分别对root和subRoot做前序遍历根 → 左 → 右同时用特殊标记如#表示空节点、用分隔符如,隔开节点值生成唯一的字符串字符串匹配在root的序列化串中查找subRoot的序列化串找到则返回true。注意两个细节必须标记空节点。如果不标记空节点root [2,3]与root [2,null,3]会得到相同的序列化结果无法区分结构差异必须使用分隔符。若不加分隔符root节点值为[12, 3]与[1, 23]可能产生歧义串。例如示例 1 中root [3,4,5,1,2]序列化为3,4,1,#,#,2,#,#,5,#,#subRoot [4,1,2]序列化为4,1,#,#,2,#,#后者显然是前者的子串。4.2 朴素匹配与 KMP 加速序列化后最简单的做法是使用朴素字符串匹配从root序列化串的每个位置开始尝试匹配subRoot串最坏时间复杂度为 $O((m n) \times n)$。而KMP 算法Knuth-Morris-Pratt可以在失配时利用已匹配前缀信息、让主串指针不回退将匹配时间复杂度降到 $O(m n)$。仓库中给出了完整的 KMP 实现见 codes/python/04_string/string_kmp.py其核心结构如下def kmp(T: str, p: str) - int: n, m len(T), len(p) next generateNext(p) # 生成 next 数组 j 0 # j 为模式串中当前匹配的位置 for i in range(n): # i 为文本串中当前匹配的位置 while j 0 and T[i] ! p[j]: # 匹配失败时模式串回退j 0 时停止回退 j next[j - 1] if T[i] p[j]: # 前缀匹配成功j 1 j 1 if j m: # 完全匹配成功返回匹配开始位置 return i - j 1 return -1 # 匹配失败返回 -1next数组的含义是next[j]记录模式串p[0: j1]中最长相等前后缀的长度。关于next数组的构建原理与 KMP 匹配过程的详细讲解见 docs/04_string/04_04_string_kmp.md。将 KMP 应用到本题整体复杂度为序列化$O(m n)$KMP 匹配$O(m n)$总时间复杂度$O(m n)$空间复杂度 $O(m n)$。相比递归匹配的 $O(m \times n)$当root节点数$m$与subRoot节点数$n$都较大时序列化 KMP 具有明显的渐进优势。不过需要注意序列化方案需要先完整遍历两棵树并构造字符串常数开销较大若root较小时递归匹配的实现在工程上往往更直接简单。五、解法三子树哈希Merkle 式自底向上标记题目标签中的「哈希函数」提示了另一种进阶思路为树中的每一棵子树计算一个哈希值然后比对哈希值。5.1 思路拆解自底向上为root中的每个节点计算「以其为根的子树」的哈希值例如def hash_subtree(node): if not node: return null return hash((hash_subtree(node.left), node.val, hash_subtree(node.right)))其中子树的哈希由「左子树哈希 当前节点值 右子树哈希」组合而成这与后序遍历左 → 右 → 根的递归顺序一致遍历细节见 docs/05_tree/05_02_binary_tree_traverse.md 2. 对subRoot计算同样的哈希值 3. 在遍历root的过程中一旦发现某棵子树的哈希值与subRoot的哈希值相等再调用isSameTree做最终确认防止哈希碰撞返回true。5.2 复杂度分析时间复杂度哈希的递归组合使得每个节点只被访问常数次整体为 $O(m n)$仅在哈希值相等时才会触发一次 $O(n)$ 的逐节点确认因此平均与最坏情况都在 $O(m n)$ 量级空间复杂度$O(m n)$用于存储各子树哈希值或递归栈。子树哈希思路本质上是一种「自底向上聚合信息」的模式与并查集、线段树等树形结构中「合并子问题信息」的思想一脉相承仓库中相关实现可参考 codes/python/05_tree/tree_unionFind.py。不过在实际面试中哈希方案需要对碰撞问题有所说明因此通常作为加分项思路呈现主流答案仍是递归匹配。六、三种解法对比与选型建议解法核心思想时间复杂度空间复杂度适用场景递归匹配双重 DFS外层枚举节点 内层isSameTree$O(m \times n)$$O(m)$通用、代码最简洁面试首选序列化 KMP树转字符串 KMP 子串匹配$O(m n)$$O(m n)$两棵树规模大、追求渐进最优子树哈希自底向上哈希 碰撞确认$O(m n)$$O(m n)$需要额外讲解哈希碰撞处理的进阶场景选型建议面试时优先给出递归匹配思路直观、实现短、无碰撞风险在回答时间复杂度的追问时可自然过渡到序列化 KMP 或子树哈希展示对 $O(m \times n)$ 上界的优化理解。七、相关题目与知识延伸本题在仓库中有完整的知识链路可以串联复习前置基础树的定义、二叉树性质与链式存储见 docs/05_tree/05_01_tree_basic.md遍历基础前序/中序/后序/层序遍历的递归与非递归实现见 docs/05_tree/05_02_binary_tree_traverse.md同源子问题LeetCode 0100「相同的树」是本题isSameTree辅助函数的独立版本见 docs/solutions/0100-0199/same-tree.md字符串匹配KMP 算法原理与next数组构建见 docs/04_string/04_04_string_kmp.md配套可运行的示例代码在 codes/python/04_string/string_kmp.py复杂度分析大 O 记号、最坏/平均复杂度约定见 docs/00_preface/00_03_algorithm_complexity.md同类题目索引本题在 0500-0599 分段的完整题解索引见 docs/solutions/0500-0599/index.md更多分类刷题清单见 docs/00_preface/00_06_categories_list.md。掌握「递归匹配」这道题的分解思路后遇到任何「判断树 A 是否包含树 B」「两棵树是否同构」「找出所有相同子树」类问题都可以复用「外层遍历 内层相等性判定」或「序列化/哈希归约到字符串/哈希问题」这两类范式。赞分享教程文档知识库【免费下载链接】AlgoNote⛽️「算法通关手册」从零开始的「算法与数据结构」学习教程200 道「算法面试热门题目」1000 道「LeetCode 题目解析」持续更新中项目地址https://gitcode.com/gh_mirrors/le/AlgoNote点击查看免费下载相关推荐30分钟上手SQL注入测试CyberStrikeAI完整实战指南30分钟上手SQL注入测试CyberStrikeAI完整实战指南 手工构造注入 payload、逐条比对 HTTP 响应、再对着 WAF 拦截日志想办法绕过—网络安全渗透测试人工智能大模型AI AgentRAG后端前端MCP 服务漏洞扫描AlgoNote 题解精讲从前序与中序遍历序列构造二叉树LeetCode 0105 · 递归分治 哈希表优化AlgoNote 题解精讲从前序与中序遍历序列构造二叉树LeetCode 0105 · 递归分治 哈希表优化 本篇为「算法通关手册」AlgoNote教程文档知识库AlgoNote 算法题解LeetCode 0106 从中序与后序遍历序列构造二叉树递归分治全解析AlgoNote 算法题解LeetCode 0106 从中序与后序遍历序列构造二叉树递归分治全解析 本文是 AlgoNote「算法通关手册」二叉树还原专题教程文档知识库上一篇Serverless-Devs项目维护与升级版本管理、依赖更新与兼容性处理下一篇S4项目深度解析从HiPPO理论到实际应用的完整路线图创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

rsuite 中如何为 disabled 禁用元素添加 Tooltip:pointer-events 覆盖与 Whisper 包装完整方案

rsuite 中如何为 disabled 禁用元素添加 Tooltip:pointer-events 覆盖与 Whisper 包装完整方案

前端UI组件 【免费下载链接】rsuite 🧱 A suite of React components . 项目地址: https://gitcode.com/gh_mirrors/rs/rsuite 点击查看 免费下载 在 rsuite 中,disabled 的按钮、输入框等元素不会响应鼠标悬停、点击与键盘聚焦&#xff0c…

2026/10/9 1:20:55 阅读更多 →
三合一充电线选购指南:内部结构、接口组合与实用场景全解析

三合一充电线选购指南:内部结构、接口组合与实用场景全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 1:19:55 阅读更多 →
用Python与PCA做异常检测:重构误差、KPCA与工程实践

用Python与PCA做异常检测:重构误差、KPCA与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 1:19:55 阅读更多 →

最新新闻

使用 LLM 为 ProxySQL 编写高质量增强版发布说明(Enhanced Release Notes)实战指南

使用 LLM 为 ProxySQL 编写高质量增强版发布说明(Enhanced Release Notes)实战指南

后端数据库负载均衡 【免费下载链接】proxysql High-performance proxy for MySQL and PostgreSQL 项目地址: https://gitcode.com/gh_mirrors/pr/proxysql 点击查看 免费下载 本文档是 ProxySQL 仓库中发布说明生成工作流的核心提示词模板 —— enhanced_prompt_t…

2026/10/9 1:48:13 阅读更多 →
video-shotcraft 协作光标编排:dialogue-duet 与 cast-ensemble 两式光标的 Remotion 实现解析

video-shotcraft 协作光标编排:dialogue-duet 与 cast-ensemble 两式光标的 Remotion 实现解析

AI 技能媒体生成视频 【免费下载链接】video-shotcraft AI video skill for Claude Code & Codex — cinematic product videos with Remotion: 152 shot recipe cards, 209 motion previews, a production-ready template 项目地址: https://gitcode.com/gh_mi…

2026/10/9 1:48:13 阅读更多 →
FreeRtos——15、查看freeRTOS堆栈使用情况——非常重要

FreeRtos——15、查看freeRTOS堆栈使用情况——非常重要

在使用freeRTOS做多任务的时候,总是担心任务会出现Bug,导致任务死机或其他异常情况,这些很多都是由于堆栈分配不合理出现的问题,那就要了解每个任务的堆栈使用情况。FreeRTOS 本身提供了专门的 API 来监控堆栈使用,核心…

2026/10/9 1:48:13 阅读更多 →
物联网开发全链路能力拆解:从设备端到云端的五大关键环节

物联网开发全链路能力拆解:从设备端到云端的五大关键环节

这几年做物联网定制项目,感触最深的一件事是:绝大多数客户对"物联网开发"的理解是破碎的。有人以为买个模组、写个固件、连上Wi-Fi就万事大吉;有人觉得只要找到能画PCB的硬件工程师,剩下的云端、APP、运维都能用现成平台…

2026/10/9 1:48:13 阅读更多 →
ESP32+GY-30光照传感器入门:I2C通信与Arduino实战教程

ESP32+GY-30光照传感器入门:I2C通信与Arduino实战教程

1. 项目概述:为什么我推荐从光照传感器开始玩ESP32很多朋友第一次接触ESP32,第一反应就是“我能用它做什么”。点灯太简单,做物联网又怕一步到位学不会,这时候一个“看得见反馈”的传感器项目就是最好的过渡。GY-30光照传感器就是…

2026/10/9 1:48:13 阅读更多 →
AI日报从0到1:筛选逻辑、工具链配置与90分钟生产流程

AI日报从0到1:筛选逻辑、工具链配置与90分钟生产流程

1. AI日报这个栏目到底在做什么做AI日报这件事,我从2024年底开始坚持到现在,中间断更过两次,一次是因为信息源太杂导致筛选成本失控,一次是因为写得太像新闻通稿自己都不想看。后来我把整个流程重新拆了一遍,才找到可持…

2026/10/9 1:47:12 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题,隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题,排查到最后发现是ZonedDateTime序列化后时区丢了,用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问:办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好,问题是工作场景经常要在几处环境之间来回切换,每次都先登录跳板机再层层代理,实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地,从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 21:13:17 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →