如何判断一棵二叉树是否对称(轴对称)?
简化版
一棵树对称,等价于它的左子树和右子树互为镜像。递归比较两棵子树:两个根值要相等,且左树的左孩子对右树的右孩子、左树的右孩子对右树的左孩子(外侧配外侧、内侧配内侧)。只要每一对都镜像匹配,整棵树就对称。也能用队列迭代,成对入队比较。
详细版
关键:对称不是「左子树等于右子树」,而是「左子树和右子树互为镜像」。镜像意味着比较时要交叉:
boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isMirror(root.left, root.right);
}
boolean isMirror(TreeNode a, TreeNode b) {
if (a == null && b == null) return true; // 都空,镜像
if (a == null || b == null) return false; // 一空一非空,不镜像
return a.val == b.val // 根值相等
&& isMirror(a.left, b.right) // 外侧:a的左 对 b的右
&& isMirror(a.right, b.left); // 内侧:a的右 对 b的左
}
以这棵对称树为例:
1
/ \
2 2
/ \ / \
3 4 4 3
比较左 2 和右 2:值相等;左2的左(3) 对 右2的右(3)✓;左2的右(4) 对 右2的左(4)✓。全部匹配,对称。
完整版教学
一、为什么是「交叉比较」而不是「同侧比较」
轴对称的含义是「以根为轴,左右翻折后能完全重合」。翻折后,左子树的最外侧会和右子树的最外侧重合,左子树的内侧和右子树的内侧重合。落到节点上就是:
- 左子树的左孩子 ↔ 右子树的右孩子(外对外)
- 左子树的右孩子 ↔ 右子树的左孩子(内对内)
所以递归时是交叉匹配 (a.left, b.right) 和 (a.right, b.left),而不是 (a.left, b.left)。这是本题最核心、也最容易写错的地方。
二、递归的三种情况
比较两个节点 a、b 是否镜像:
- 都为空:镜像成立(对称位置都没节点)。
- 一个空一个非空:结构不对称,返回 false。
- 都非空:值必须相等,且左右交叉的两对子节点也各自镜像。
递归出口是前两种,第三种把问题分解到更小的子问题。
三、迭代解法:成对入队
用队列,每次成对取出应当镜像的两个节点比较:
boolean isSymmetric(TreeNode root) {
if (root == null) return true;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root.left); q.offer(root.right);
while (!q.isEmpty()) {
TreeNode a = q.poll(), b = q.poll();
if (a == null && b == null) continue;
if (a == null || b == null || a.val != b.val) return false;
q.offer(a.left); q.offer(b.right); // 外侧一对
q.offer(a.right); q.offer(b.left); // 内侧一对
}
return true;
}
入队顺序保证每次 poll 出来的两个正好是「应当镜像」的一对。注意入队顺序仍是交叉的(a.left 配 b.right)。
四、和「相同的树」「翻转」的关系
- 判断两棵树相同:对应位置同侧比较
(a.left,b.left)、(a.right,b.right),值都相等。 - 判断对称:一棵树的左右子树交叉比较(互为镜像)。
- 翻转二叉树:把树改成镜像。
三者是一组「围绕镜像/相等」的题:判断对称 = 判断「左子树 和 翻转后的右子树 是否相同」。理解一个就能推出其他。
五、复杂度
- 时间 O(n):每个节点被比较一次。
- 空间 O(h):递归栈深度为树高(迭代版是队列宽度 O(w))。
六、常见误区与追问
| 题型 | 比较方式 | 是否修改树 |
|---|---|---|
| 相同的树 | 同侧比较:左对左、右对右 | 不修改 |
| 对称二叉树 | 交叉比较:左对右、右对左 | 不修改 |
| 翻转二叉树 | 每个节点交换左右孩子 | 修改结构 |
记忆钩子:对称不是「左右子树长得一样」,而是「左子树照镜子后等于右子树」。
- 误区:判断对称时同侧比较左右孩子。 同侧比较是判断两棵树是否相同;对称必须交叉比较外侧和内侧。
- 误区:只比较结构不比较节点值。 镜像要求结构对应且节点值相等,少任意一个条件都不成立。
- 误区:判断对称需要先翻转一棵子树。 可以这么想,但实现上通常直接递归比较镜像位置,避免修改原树。
- 追问:空树是否对称? 通常认为空树对称;根为空直接返回
true。 - 追问:迭代版为什么要成对入队? 每次出队的两个节点是一组镜像候选,必须同时比较空值、节点值和下一层交叉孩子。
- 追问:复杂度是多少? 时间 O(n),每个节点最多比较一次;递归空间 O(h),迭代队列最坏 O(w)。
七、加强记忆
树对称 ⟺ 左右子树互为镜像。递归比较两节点:值相等,且交叉匹配——左树的左对右树的右(外对外)、左树的右对右树的左(内对内)。交叉是关键,别写成同侧。也可用队列成对入队比较。它和「判断相同树(同侧比较)」「翻转」是一组镜像题。时间 O(n)。