题目描述
思路解析
一句话答案:LeetCode 101 对称二叉树的标准解是递归比较镜像对:定义辅助函数 mirror(a, b),让 a 的左孩子对 b 的右孩子、a 的右孩子对 b 的左孩子交叉比较,值不等或一空一有立即返回 false。每个节点只被比较一次,时间 O(n),空间为递归栈深 O(h),h 是树高。
这道题真正在问什么
题目给一棵二叉树,问它是否关于根节点这条中轴左右镜像对称。注意「对称」不等于「左右子树长得一样」:想象把树沿中轴对折,左半边最外侧的节点要正好压在右半边最外侧的节点上。题目示例 [1,2,2,null,3,null,3] 里,两棵子树都是「2 带一个右孩子 3」,长得一模一样,答案却是 false——对折之后两个 3 的位置根本对不上。要判断的是位置加数值的双重对应。
为什么直接比较左右子树行不通
最直觉的做法是逐层取出节点检查每层是否回文,可行但要小心补 null 占位,写起来啰嗦。更本质的观察是:整棵树对称,当且仅当左子树与右子树互为镜像。而镜像的含义是外侧对外侧、内侧对内侧——照镜子时,左子树的最外缘正对右子树的最外缘。所以比较必须交叉进行:左边节点的左孩子,要去和右边节点的右孩子配对。这一步交叉是全题的题眼,也是它区别于「判断两棵树是否相同」的地方。
递归函数为什么要同时带两个节点
单参数的递归表达不了「这棵子树和另一棵子树互为镜像」这种跨树关系,所以要定义双参数函数 mirror(a, b),含义是「以 a、b 为根的两棵子树互为镜像」,入口调用 mirror(root.left, root.right)。它的子问题结构和自身完全一致:a 与 b 镜像,等价于两者值相等,且 a.left 与 b.right 镜像、a.right 与 b.left 镜像。问题规模每递归一层就缩小到下一层的镜像对,天然收敛。
三个判断条件为什么缺一不可
递归里按顺序判三种情况:a、b 都为空返回 true——对折后两边都没有东西,天然对齐,这是递归的触底出口;一空一有返回 false——结构已经错位,这是只比较数值时最容易漏掉的情况;值不相等返回 false。三关都过了,才继续往里递归两组镜像对。正确性来自「处处相等」:任何一对镜像位置不匹配,false 会沿递归链一路短路传回根部,整棵树即判不对称。
复杂度怎么算,哪些边界会翻车
时间 O(n):每个节点至多参与一次镜像比较,n 为节点数。空间 O(h):递归栈深等于树高,平衡树约 O(log n),退化成链最坏 O(n)。最常见的错误是把递归写成 mirror(a.left, b.left)——不交叉,验证的就是「左右子树相同」而非镜像;其次是只比完根的左右孩子就下结论,上层对称不代表深层对称,必须递归到底。空树按约定返回 true。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
镜子的核心是外侧对外侧、内侧对内侧:照镜子时,左子树最外缘正对右子树最外缘。所以递归里 a 的左要去和 b 的右比,a 的右去和 b 的左比——这一步交叉是全题的题眼。
准备 · 整棵树(根=轴):先看清这棵树:根 1 是对称轴(绿)。沿这条轴对折,左半边的 2-3-4-5-6-7-8 该和右半边的 2-4-3-8-7-6-5 一一照镜子。下面从根的左、右孩子这一对开始,逐对比较。
比较镜像对 2 ⟷ 2:照镜子时,左半边的某个节点 2 应当正对右半边对称位置的节点 2。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 2 的左孩子 ⟷ 2 的右孩子,以及 2 的右 ⟷ 2 的左。
比较镜像对 3 ⟷ 3:照镜子时,左半边的某个节点 3 应当正对右半边对称位置的节点 3。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 3 的左孩子 ⟷ 3 的右孩子,以及 3 的右 ⟷ 3 的左。
比较镜像对 5 ⟷ 5:照镜子时,左半边的某个节点 5 应当正对右半边对称位置的节点 5。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 5 的左孩子 ⟷ 5 的右孩子,以及 5 的右 ⟷ 5 的左。
叶子 5 ⟷ 5 之下都空 → 收口:这对叶子 5 与 5 之下都是空(没有孩子)。镜像边界规则——两边都空就算对称成立,递归在这里触底返回 true,这条分支安全收口。
比较镜像对 6 ⟷ 6:照镜子时,左半边的某个节点 6 应当正对右半边对称位置的节点 6。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 6 的左孩子 ⟷ 6 的右孩子,以及 6 的右 ⟷ 6 的左。
叶子 6 ⟷ 6 之下都空 → 收口:这对叶子 6 与 6 之下都是空(没有孩子)。镜像边界规则——两边都空就算对称成立,递归在这里触底返回 true,这条分支安全收口。
比较镜像对 4 ⟷ 4:照镜子时,左半边的某个节点 4 应当正对右半边对称位置的节点 4。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 4 的左孩子 ⟷ 4 的右孩子,以及 4 的右 ⟷ 4 的左。
比较镜像对 7 ⟷ 7:照镜子时,左半边的某个节点 7 应当正对右半边对称位置的节点 7。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 7 的左孩子 ⟷ 7 的右孩子,以及 7 的右 ⟷ 7 的左。
叶子 7 ⟷ 7 之下都空 → 收口:这对叶子 7 与 7 之下都是空(没有孩子)。镜像边界规则——两边都空就算对称成立,递归在这里触底返回 true,这条分支安全收口。
比较镜像对 8 ⟷ 8:照镜子时,左半边的某个节点 8 应当正对右半边对称位置的节点 8。先比较它们的值——只有值先相等,才有资格继续往里比。
这一对相等 → 对称 ✓:值相等!把这一对标蓝(已确认对称)。但还没完——镜像要求外侧对外侧、内侧对内侧:接下来要比 8 的左孩子 ⟷ 8 的右孩子,以及 8 的右 ⟷ 8 的左。
叶子 8 ⟷ 8 之下都空 → 收口:这对叶子 8 与 8 之下都是空(没有孩子)。镜像边界规则——两边都空就算对称成立,递归在这里触底返回 true,这条分支安全收口。
答案 · 对称 = true:所有镜像对都比完了:每一对(外侧、内侧)都相等,没有一处错位。整棵树关于根这条轴完全对称,返回 true。
反例 · 一处不等即不对称:反例:只要把右半边一个节点从 7 改成 99,它和左半边对称位置的 7 一比就不相等(红)。镜像递归遇到第一处不等立刻短路返回 false——对称是「处处相等」,一票否决。
参考代码
class Solution { public boolean isSymmetric(TreeNode root) { if (root == null) return true; // 空树对称 return mirror(root.left, root.right); // 判左右子树互为镜像 } private boolean mirror(TreeNode a, TreeNode b) { if (a == null && b == null) return true; // 都空 → 对称 if (a == null || b == null) return false; // 一空一有 → 不对称 if (a.val != b.val) return false; // 值不等 → 不对称 return mirror(a.left, b.right) // 外侧对外侧 && mirror(a.right, b.left); // 内侧对内侧 }}复杂度
- 时间:O(n),每个节点最多被比较一次
- 空间:O(h),递归栈深 = 树高 h;最坏(链)O(n),平衡 O(log n)
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
二叉树的中序遍历
LeetCode 94 · 简单 · 沿着 树 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题