对称二叉树:沿着中轴折一下
左右子树各自对称不等于整棵树对称——沿中轴折叠,看清交叉配对。
对称树的检查,不是比较「左子树」与「右子树」,而是让树沿着中轴对折:左子树的左孩子与右子树的右孩子互相对应。递归表达就是一句话——两棵树 p、q 对称,当且仅当 p 的值等于 q 的值,且 p 的左子树与 q 的右子树对称、p 的右子树与 q 的左子树对称。
给你一棵二叉树的根节点,问它是否沿中轴左右对称。所谓对称,就是想象在这棵树正中立一面镜子,镜子里外的树完全重合。
很多人的第一反应是「检查左子树和右子树是否相同」——这个直觉看似自然,却是错的:左子树和右子树各自的结构,与它们镜像对称时应该的结构,完全是两回事。
这篇文章会带你重走这条发现之路:先撞上直觉陷阱,再完成一次「沿中轴折叠」的视觉翻转,看清真正的交叉配对关系,最后落成递归与队列两种实现。
先明确问题
给你一棵二叉树的根节点 root,判断它是否沿中轴左右对称。下面这棵树是对称的:
1
/ \
2 2
/ \ / \
3 4 4 3
注意第三层:左边是 3、4,右边是 4、3——不是「相同」,而是「互为镜像」。
而这一棵不对称:
1
/ \
2 2
\ \
3 3
左边 2 只有右孩子 3,右边 2 也只有右孩子 3——左右子树结构完全「相同」,但树并不对称。
直觉陷阱:左右子树相同≠对称
上面的不对称例子说明了一个深刻的道理:左右子树「相同」是错的判据。左边 2 的右孩子是 3,镜像对称要求右边 2 的左孩子是 3;但右边 2 的左孩子是空。
换句话说,镜像比较是「交叉」的:左子树的右孩子,要对齐右子树的左孩子;左子树的左孩子,要对齐右子树的右孩子。用「相同」来比,就是把交叉对齐错配成了同侧对齐。
这正是递归/树的经典陷阱:直觉给出的问题结构,与算法真正需要的问题结构不一致。
直觉「相同」比较的是同侧,「对称」比较的是异侧。一字之差,两个世界。
视觉翻转:沿中轴折叠
现在做一次视觉翻转:不要问「左右子树相同吗」,而是想象这棵树是一张纸,沿着中轴(根节点所在的那条竖线)对折。
对折之后,纸的左半边盖在右半边之上。原来左侧的节点,会落到右侧的某个位置上——落点就是它的「镜像位」。树对称,当且仅当每个节点都正好落在某个同值节点的位置。
看看第三层:左边 2 的右孩子 3(下标 4),折叠后落在哪里?它对应的镜像位,是右边 2 的左孩子的位置(下标 5)。可右边 2 的左孩子是空的——没接住,于是树不对称。
这样,「对称」就从一个抽象的性质,变成了一件可以动手验证的事:把左半边折过去,看每个节点有没有落到它该落的地方。
沿中轴对折这张「树的纸」,看左半边的每个节点落在哪里。
交叉配对:左的左 ↔ 右的右
折叠的落点关系可以用一句话说清:左子树的左孩子 ↔ 右子树的右孩子,左子树的右孩子 ↔ 右子树的左孩子。这是「交叉」的。
把它写成递归:两棵树 p 和 q 对称,当且仅当三件事同时成立——
· p 的值等于 q 的值;
· p 的左子树 与 q 的右子树 对称;
· p 的右子树 与 q 的左子树 对称。
而空节点是递归的天然边界:两个都空 → 对称;一个空一个不空 → 不对称。
模式一旦把问题切成「左右交叉」的小对子,递归就只是把这些对子一个个验证。
递归实现
递归版几乎是交叉配对的直接翻译。check(p, q) 判断「两棵子树互为镜像」,isSymmetric(root) 就是 check(root.left, root.right)。
边界处理:都空返回 true;一个空返回 false。
核心:p.val == q.val,并且 check(p.left, q.right) 与 check(p.right, q.left) 都为 true。
队列实现:广度地验证对子
递归是深度优先地验证每一对对子。也可以改成广度优先:用队列按层存放「下一批要比较的对子」。
初始把 (root.left, root.right) 入队。每次出队一对,若都空则继续;若一个空则不相等;若值不等则不相等;否则把 (p.left, q.right) 和 (p.right, q.left) 两对依次入队。队列清空即对称。
两种实现的比较顺序不同,但比较的对子集合完全一样——因为「哪些节点应该互为镜像」是树本身决定的,与遍历顺序无关。
七行 Go:递归
func isSymmetric(root *TreeNode) bool {return check(root.Left, root.Right)}func check(p, q *TreeNode) bool {if p == nil && q == nil { return true }if p == nil || q == nil { return false }return p.Val == q.Val &&check(p.Left, q.Right) &&check(p.Right, q.Left)}
1isSymmetric 只是把整棵树的问题交给 check(root.Left, root.Right)。
2check 判断两棵子树是否互为镜像。
3两个都空:没有可比的,当然对称。
4一个空一个不空:落点接不住,不对称。
5核心:根值相等,并且交叉配对成立——左的左 vs 右的右,左的右 vs 右的左。
总结
对称,是交叉配对:左的左 ↔ 右的右。
- 「左右子树相同」是直觉陷阱;镜像要求的是异侧对齐。
- 沿中轴折叠:每个节点要落到它的镜像位,这就是对称的含义。
- 递归一句话:值相等 + 左的左 vs 右的右 + 左的右 vs 右的左。