对称二叉树 LeetCode 101:递归与迭代解法及常见误区

发布时间:2026/10/29 9:33:25

对称二叉树 LeetCode 101:递归与迭代解法及常见误区 刷 LeetCode 的人大概率会在二叉树专题里撞上这道题——判断对称二叉树题号 101英文名 Symmetric Tree。它不是那种一眼就让人发怵的难题但特别有代表性一道题同时考了递归设计、树的结构认知和边界条件处理。我第一次做的时候以为只要分别判断左右子树对称就行结果提交之后被用例教育了。今天这篇文章就把这道题拆开揉碎从题目到底在问什么到递归和迭代两种通解再到我实际踩过的几个坑一次讲清楚。无论你是正在准备算法面试的开发者还是刚系统学二叉树数据结构的初学者看完之后应该都能把对称这两个字真正翻译成代码。你可能已经在各种刷题清单里见过它也知道答案大概长什么样但很多时候只是背会了并没有真正理解为什么这么写。这也是我写这篇的原因与其对着答案默写不如把背后的镜像关系、递归终止条件、队列配对方式都弄明白。后面我还会提到空树、单节点、链式树这些边界场景很多面试官就喜欢在这些小地方挖坑。1. 先搞懂题目到底在问什么1.1 定义什么叫作对称直观来说对称二叉树就是一棵树以根节点所在的中轴线为轴左右两边是镜像的。比如下面这棵树1 / \ 2 2 / \ / \ 3 4 4 3左子树和右子树从根节点 1 往下看是互相照镜子的关系左子树的左侧 3对应右子树的右侧 3左子树的右侧 4对应右子树的左侧 4。你把它想象成一个人站在镜子前左手对应镜中的右手右手对应镜中的左手。判断对称本质上就是在验证左子树和右子树互为镜像。很多人第一次会把对称和相等混在一起。相等是左边一棵树和右边一棵树完全一样对称则要求交叉位置一样。给你一个反例稍微改一下就能看出差别1 / \ 2 2 / \ / \ 3 4 3 4这棵树的左右子树其实是完全相同的但它并不对称因为左下角的 3 应该对应右下角的 4现在却对应到了 3。这就是交叉比较和相同位置比较的本质区别。1.2 对称、相同、翻转三者关系刷到这道题的时候很多教程会顺便提到另外两题LeetCode 100 相同的树以及 LeetCode 226 翻转二叉树。这三题放在一起对比着看会非常清楚。题目核心问题比较方式100. 相同的树两棵树是否完全一样p.left 与 q.left 比p.right 与 q.right 比226. 翻转二叉树把一棵树所有左右子树交换不比较只负责修改结构101. 对称二叉树一棵树自己是否镜像对称左子树的左孩子与右子树的右孩子比左子树的右孩子与右子树的左孩子比这里有一个全文最核心的公式。判断一棵树是否对称等价于判断 root.left 和 root.right 是否互为镜像可以写成isMirror(p, q) (p.val q.val) isMirror(p.left, q.right) isMirror(p.right, q.left)看到这个公式你大概就能明白为什么这道题和相同的树几乎共享同一套递归模板只是比较方向从直比变成了交叉比。另外还有一种等价思路先把树翻转再和原树比较是否相同。这种思路在理解上很有帮助但要注意别直接修改原树要么复制一份要么在递归参数里做虚拟翻转。面试时能主动提到这个等价关系往往是加分项。1.3 边界约定空树对称吗LeetCode 的判定是空树返回 true。很多人第一次写会在 isSymmetric 入口直接按if not root: return True处理也有人觉得空树没有对称性应该返回 false。但刷题平台的标准答案就是空树对称逻辑上也说得通空树没有左右子树自然不存在不对称的节点对。面试时最好主动问一句空树怎么算一般都会按 true 处理你确认一下反而显得严谨。至于单节点树root.left 和 root.right 都是 None辅助函数isMirror(None, None)会返回 true所以单节点天然对称。这两个边界在递归终止条件里其实已经天然涵盖了不需要在入口额外处理单节点的情况。2. 把镜像翻译成递归逻辑2.1 核心转换比较的对象是两棵子树写这道题最容易卡住的地方是不知道该拿谁和谁比。很多人第一反应是判断一个节点的左右孩子是否相等马上发现自己根本写不下去。原因在于对称是一个全局性质只盯着一个节点看是看不出来的。递归的精髓就是把一棵树的问题转化为两个子树的问题。既然是对称那比较对象就天然是两个节点。1 / \ L R在 L 和 R 的下一层镜像对应关系是L.left 对应 R.rightL.right 对应 R.left。为什么是交叉的你想象镜子立在 L 和 R 之间。L 的左外侧是整棵树的左外侧它在镜子里应该对应到 R 的右外侧也就是 R.right。同理L 的右内侧对应 R 的左内侧。所以才有isMirror(p.left, q.right)这种看起来有点拗口的写法它不是笔误而是镜像关系的直接翻译。2.2 递归函数的三个终止条件递归函数isMirror(p, q)的逻辑可以分为四步前三个是终止条件最后是递归推进如果 p 和 q 都是空返回 true。两棵空子树当然是镜像的。如果 p 和 q 中只有一个为空返回 false。一棵有节点、一棵没有节点结构已经不一样了。如果 p.val 和 q.val 不相等返回 false。继续递归比较 p.left 与 q.right以及 p.right 与 q.left两个结果都要成立。这里极其重要的是顺序。必须先处理空值再去访问 val。如果一上来就写if p.val ! q.val而 p 恰好是 None程序会直接抛空指针异常。这个顺序问题我在第四节还会展开说因为它是新手最容易踩的坑。有经验的开发者可能会写更紧凑的版本if not p or not q: return p is q用p is q判断两个对象是否同为 None。这种写法没问题但初学者建议还是拆成两行可读性更高也不容易出错。2.3 迭代解法的队列设计递归写完之后面试官经常追问一句如果不让用递归你怎么实现 这时候就需要迭代解法。迭代解法不需要调用栈而是用一个队列显式地维护待比较的节点对。核心思路是每次从队列里取出两个节点比较它们的值然后按镜像位置把它们的下一层节点成对放回去。具体来说初始化队列把 root.left 和 root.right 作为第一对放进去。循环直到队列为空。每次取出两个节点 p 和 q。如果 p 和 q 都是空继续下一轮。如果 p、q 中有一个为空或者值不相等直接返回 false。把 p.left 和 q.right 放进去再把 p.right 和 q.left 放进去。这里用队列还是栈其实都行队列是 BFS 的感觉栈是 DFS 的感觉关键是成对取出、镜像配对放回这个约束不变。Python 里用deque而不是 list因为deque.popleft()是 O(1)而list.pop(0)是 O(n)性能差一个量级。这个细节在刷题时可能无所谓但工程上写代码我会一直保持这个习惯。3. 手把手写出两种 AC 解法3.1 先准备测试用例在刷题平台提交之前建议先在本地跑几个用例。先定义 TreeNode然后手工构造几棵树来验证。class TreeNode: def __init__(self, val0, leftNone, rightNone): self.val val self.left left self.right rightLeetCode 环境里 TreeNode 是现成的本地练习需要自己写一遍。手边最快的方式是直接按照结构一层层 new 出来比如构造一个对称树def build_symmetric_tree(): n3 TreeNode(3) n4 TreeNode(4) n4m TreeNode(4) n3m TreeNode(3) n2 TreeNode(2, n3, n4) n2m TreeNode(2, n4m, n3m) return TreeNode(1, n2, n2m)建议至少准备四类用例空树、单节点、形状不对称但值恰好回文的树、深层链式树。深层链式树主要用来测试迭代版对比递归版的空间表现面试时提到这一点会很加分。3.2 递归版代码与逐行讲解递归版是这道题最自然的解法完整代码如下class Solution: def isSymmetric(self, root: TreeNode) - bool: if not root: return True return self._is_mirror(root.left, root.right) def _is_mirror(self, p: TreeNode, q: TreeNode) - bool: if not p and not q: return True if not p or not q: return False if p.val ! q.val: return False return self._is_mirror(p.left, q.right) and self._is_mirror(p.right, q.left)逐行看入口函数isSymmetric只负责两件事空树返回 true否则把左右子树交给辅助函数。辅助函数_is_mirror才是真正干活的。前两个 if 处理空指针情况第三个 if 处理值不等的情况。最后一行是递归的核心p.left和q.right一组p.right和q.left一组这个交叉必须写对。Python 的and是短路求值。如果第一组_is_mirror(p.left, q.right)已经返回 false那第二组根本不会执行函数会立刻返回 false。这个特性对树这类找到一个反例就能提前结束的问题非常友好。用前面那棵对称树来推演一下isSymmetric(root)会调用_is_mirror(2, 2)接着比较_is_mirror(3, 3)和_is_mirror(4, 4)。注意这里比较的分别是左子树的左孩子和右子树的右孩子、左子树的右孩子和右子树的左孩子如果写成了左对左、右对右那判断的就是相同的树而不是对称的树了。3.3 迭代版代码与逐行讲解迭代版的核心是队列完整代码如下from collections import deque class Solution: def isSymmetric(self, root: TreeNode) - bool: if not root: return True queue deque([root.left, root.right]) while queue: p queue.popleft() q queue.popleft() if not p and not q: continue if not p or not q or p.val ! q.val: return False queue.append(p.left) queue.append(q.right) queue.append(p.right) queue.append(q.left) return True这里最需要理解的是入队顺序。每次取出两个节点后我们按照镜像关系把它们的下一层放进去先放 p.left 和 q.right再放 p.right 和 q.left。因为队列是先进先出所以下一次取出的恰好又是一对镜像节点。如果你把入队顺序写成p.left, q.left, p.right, q.right那整个算法就从判断镜像退化成了判断相同子树。这种错误视觉上很难发现因为代码结构完全一样只是顺序改了一下。我自己的排查技巧是找一棵三层的树在纸上手动推演队列的进出很快就能定位问题。不要在脑子里空想画出来最快。如果想改成栈只需要把popleft()换成pop()逻辑完全一致。面试时说出队列和栈都可以只是遍历顺序不同这个点显得你理解比背答案高一个层次。3.4 复杂度推导与面试话术复杂度分析是面试必问这里我给出完整的推导思路。时间复杂度整棵树的每个节点最多被访问一次因为每一对节点只比较一次节点总数是 n所以时间复杂度是 O(n)。空间复杂度要分情况说递归版空间消耗在调用栈上栈的深度等于树高 h。最坏情况是树退化成一条链h n空间 O(n)。平衡二叉树情况下满二叉树有 n 2^(h1) - 1所以 h log2(n1) - 1空间 O(log n)。迭代版空间消耗在队列上队列里最多同时存放某一层的所有节点。最坏情况是完全二叉树最后一层大约有 (n1)/2 个节点所以空间也是 O(n)。不过它不受递归深度限制树特别深时更稳妥。我一般会在面试里这样说递归版时间 O(n)空间 O(h)h 是树高最坏 O(n)迭代版时间 O(n)空间 O(n) 量级但避免了递归栈溢出的风险。如果是一棵可能很深的树我倾向于选迭代版。4. 我踩过的坑与排查技巧4.1 误区一只判断左右子树各自对称这是我第一次提交犯的错误代码写成这样class Solution: def isSymmetric(self, root: TreeNode) - bool: if not root: return True return self.isSymmetric(root.left) and self.isSymmetric(root.right)看起来好像也没什么问题左边是对称的右边也是对称的那整棵树不就对称吗实际上这是错的。这个判据只说明左右子树各自拥有对称结构完全没有比较左右子树之间是否存在镜像关系。我给你一个反例左子树是一棵教科书式对称树右子树也是一棵教科书式对称树但两者整体并不对称。比如左子树左下角挂着 4右子树右下角挂着 6这两个位置本应是镜像对应的却对不上而左子树、右子树自己看都是对称的。放到代码里isSymmetric(root.left)和isSymmetric(root.right)都返回 true整体却 false。所以这道题必须引入比较两棵子树的辅助函数不能只递归地调用 isSymmetric 自己。判断一棵树的整体对称本质上不是两个独立子问题的叠加而是一次跨子树的联合判断。4.2 误区二空节点判断顺序出错看这个错误版本def _is_mirror(self, p, q): if p.val ! q.val: return False if not p and not q: return True ...第一行就会炸。因为 p 完全可能是 NoneNone 没有 val 属性。在 Python 里会抛AttributeError: NoneType object has no attribute val在 Java/C 里对应 NullPointerException。正确的顺序是先把空值的几种情况全部排除再访问值属性。这也是我写任何二叉树递归函数都遵守的习惯先处理空指针再做值比较最后递归推进。顺序不对程序跑起来就是各种隐蔽的边界报错。还有一个小细节有人喜欢写紧凑版if not p or not q: return p is q这里必须用is而不是。因为可能触发对象的重载比较逻辑而我们这里想确认的仅仅是两个引用都是 None。4.3 误区三迭代入队顺序写错迭代版代码里入队顺序是p.left, q.right, p.right, q.left但很多人会顺手写成p.left, q.left, p.right, q.right。前者是比较镜像后者是比较相同。当初我就是这么写错的debug 了很久。问题在于相同子树的判断在恰好所有节点值都对称的样例上也可能通过一部分但在真正不对称的样例上就会暴露。比如一棵树左右子树完全相同但交叉位置不匹配按相同位置比较会一路相等最后错误地返回 true而正确答案是 false。排查方法很简单构造一棵三层的小树手动走一遍队列。用文字推演可能有点抽象但在纸上画两支箭头箭头一端是p.left另一端必须指到q.right画完你永远不会再写错。4.4 层序遍历回文陷阱与速查表还有一种思路很诱人既然对称那把树做层序遍历每一层序列应该都是回文。这个判断在不省略空节点的前提下是正确的但一旦省略空节点就会误判。举个例子1 / \ 2 2 \ \ 3 3不带空节点的层序结果是第一层 [1]第二层 [2,2]第三层 [3,3]。三个序列看起来都是回文但这棵树并不对称。因为左子树的 3 出现在右侧右子树的 3 也出现在右侧两个节点都在内侧镜像关系就不成立。如果把空节点也作为占位符第二层就变成 [2,2]第三层变成 [null,3,null,3]立刻就能看出不是回文。所以层序回文方案可以成立但实现时必须带着 null 占位还得分层处理复杂度比递归和迭代都高。我建议把它作为一道独立的小练习理解原理真正面试做题还是优先递归和迭代。最后整理一份速查表方便排查症状可能原因修复方式左右孩子值都对但整体误判比较方向写成了左对左、右对右改成交叉比较p.left 对 q.right空树返回 false入口边界写错if not root: return True深树栈溢出或递归超时递归深度过大改用队列/栈迭代版层序每层回文但整体不对称空节点未占位不要用层序方案或严格带 null 占位空节点报 AttributeError / NullPointerException终止条件顺序不对先判断是否为空再访问 val5. 面试现场与延伸思考5.1 三步讲法让面试官点头这道题在面试里出现频率很高我建议按三步来讲整体非常有条理。第一步确认边界。开口先问空树我们按 true 处理对吧这样单节点树也会自然返回 true。 这句话一出来面试官就知道你考虑问题全面。第二步画递归公式。直接在纸上写核心转换把一棵树拆成两棵子树isMirror(p, q) p.val q.val isMirror(p.left, q.right) isMirror(p.right, q.left)。配合一个小例子画两三层树把交叉箭头标出来。第三步主动补复杂度并给出迭代方案。可以说时间 O(n)递归空间 O(h)最坏 O(n)。为了避免递归栈溢出我可以改成队列迭代实现空间 O(n)。 主动送迭代版比等面试官追问效果好得多。我还在实际面试中遇到过一次追问如果树是一条深度一万的链递归会不会有问题这时候直接说我会用迭代版队列内存的节点数是有限的不会撑爆调用栈基本就稳了。5.2 关联题目与扩展方向这道题最值得做的关联题有两道LeetCode 100 相同的树以及 LeetCode 226 翻转二叉树。把三题放一起看你会发现比较两棵树是一个可复用的模板只是比较方向不同。100 题是直比101 题是交叉比226 题是改造结构。刷完这三题二叉树递归的基本功会扎实很多。往深了想这类镜像比较思想还能扩展到工程场景。例如某种配置树要求左右分支结构对称或者表达式树需要校验左右运算树结构是否镜像相容。虽然实际业务里直接要求树对称并不常见但如何把两个结构之间的关系拆成递归式这种方法论在很多嵌套数据结构校验里都能派上用场。如果你想在面试中展示超出平均水平的理解还可以提一句对称二叉树可以通过 Morris 遍历优化到 O(1) 空间只不过实现复杂工程必要性不大。点到为止即可不用展开因为面试官大概率也只是想听你会不会。我自己刷这道题最大的收获不是记住镜像配对公式而是养成了一套写递归树的习惯先列终止条件再决定递归参数怎么传最后才是返回值。后来遇到 LeetCode 100、226甚至一些回溯类问题这个习惯都帮我少踩了很多坑。顺便说一句如果你第一次写迭代版强烈建议在纸上手动走一遍队列的进出我当时就是因为没走把 right 和 left 的顺序写反白白 debug 了快二十分钟。对称二叉树这道题不大但值得慢慢品。
延伸阅读

更多相关文章

2026/10/25 22:05:38

华为OD Python面试高频八股文:内置对象、GIL与机考模板全解析

华为OD的Python面试,八股文到底背到什么程度才算过关?最近好几个准备OD机考的朋友都问过我这个。说实话,这个问题的答案有点反直觉:单纯背结论很难过关,但完全不背、指望临场发挥,同样容易翻车,…

2026/10/28 6:49:49

V2G电动汽车充电站模型拆解:从原理到Python仿真与关键参数调优

简介:一份基于V2G(车辆到电网)的电动汽车充电站模型专业文献,适用于新能源汽车、智能电网与电力系统方向的工程师、研究生及高校师生。内容围绕智能充电站利用电动汽车电池作为电网储能装置的思路,详细设计了基于模糊逻…

2026/10/29 9:29:30

MongoDB分布式事务实现与优化实践

1. MongoDB分布式事务的核心挑战与实现路径 在分布式数据库系统中实现事务一致性,就像试图让一群素未谋面的陌生人同步完成一场复杂的舞蹈表演。MongoDB从4.0版本开始支持副本集级别的分布式事务,到4.2版本进一步扩展为分片集群支持,这个演进…

2026/10/29 9:29:30

品牌优化如何提升AI推荐系统的效果与转化率

1. 品牌优化在AI推荐系统中的真实价值 上周和几个做电商的朋友聊天,他们都在纠结同一个问题:花大价钱做的品牌优化,在AI主导的推荐系统里到底有没有用?这个问题确实值得深挖。现在的推荐算法早已不是简单的关键词匹配,…

2026/10/29 9:29:30

量化CTA策略全框架:选型、因子、组合风控与回测验收

简介:《量化CTA策略:理念、框架与实战》是一份面向量化投研人员、期货策略学习者及资产配置从业者的专题PDF,围绕管理期货策略的概念、研究框架与落地实践展开,帮助读者建立从策略识别到组合配置的系统认知。文件包仅含1个PDF&…

2026/10/29 9:29:30

大模型训练数据全链路:语料清洗、配比、合成与验收

简介:《阿里研究院2024大模型训练数据白皮书》以PDF形式呈现,面向大模型研发、数据治理与政策研究读者,系统梳理训练数据从重要性、类型划分到高质量标准、合成数据供给及合规治理的完整议题。压缩包内共1个PDF文件,大小约11.93MB…

2026/10/29 9:29:30

性能测试核心方法论与JMeter实战指南

1. 性能测试的本质与价值 第一次接触性能测试是在2013年,当时我们上线的一个电商系统在双十一当天崩溃了。事后分析发现,系统在300并发用户时响应时间就开始急剧上升,而实际峰值达到了2000并发。这个惨痛教训让我深刻认识到:性能测…

2026/10/29 9:24:30

MacBook Pro最佳视频播放器IINA安装与优化指南

1. 为什么选择IINA作为MacBook Pro的默认播放器 作为MacBook Pro用户,你可能已经厌倦了系统自带的QuickTime Player的各种限制。我用了整整三年时间测试了市面上几乎所有主流Mac视频播放器,最终锁定IINA作为主力播放器。这款由国内开发者独立开发的开源播…

2026/10/28 14:42:12

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/28 15:28:56

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/28 3:32:29

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/29 0:03:42

学生选课系统DFD图绘制指南:边界划分与分层校验实战

简介:文档以顶层与第一层DFD图为核心,完整刻画学生选课系统的逻辑模型,面向软件工程课程设计、系统分析学习及毕设前期建模人群。顶层图界定系统边界,外部实体包括管理员、教师和学生三类角色;第一层图进一步细化用户登…

2026/10/29 0:03:42

JIRA敏捷落地:Scrum角色权限、Sprint与燃尽图避坑指南

简介:围绕“基于JIRA的敏捷开发项目管理”这一主题,面向产品负责人、Scrum Master及研发测试成员,梳理Scrum角色分工、迭代流程与JIRA工具配置的对应关系,适合初、中级团队导入敏捷管理时参考。压缩包共1个docx文件,大…

2026/10/29 0:03:42

SEMI S8人机工程验收实战:从条款拆解到现场实测的完整指南

简介:面向半导体设备设计、制造与运维工程师及安全合规人员,这份资料聚焦SEMI标准体系下的设备安全规范,重点收录SEMI S8-0308《半导体制造设备人体工程学工程安全指南》。内容围绕设备设计原则、错误与事故预防、人体工程学适配三大主线展开…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑