当前:LC101 · 对称二叉树 · 首次出现于 Day 20 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC101 · Symmetric Tree · 树 / 递归

对称二叉树:沿着中轴折一下

左右子树各自对称不等于整棵树对称——沿中轴折叠,看清交叉配对。

对称树的检查,不是比较「左子树」与「右子树」,而是让树沿着中轴对折:左子树的左孩子与右子树的右孩子互相对应。递归表达就是一句话——两棵树 p、q 对称,当且仅当 p 的值等于 q 的值,且 p 的左子树与 q 的右子树对称、p 的右子树与 q 的左子树对称。

时间 O(n)空间 O(h)结论先行 · 全文约 6 节
导读

给你一棵二叉树的根节点,问它是否沿中轴左右对称。所谓对称,就是想象在这棵树正中立一面镜子,镜子里外的树完全重合。

很多人的第一反应是「检查左子树和右子树是否相同」——这个直觉看似自然,却是错的:左子树和右子树各自的结构,与它们镜像对称时应该的结构,完全是两回事。

这篇文章会带你重走这条发现之路:先撞上直觉陷阱,再完成一次「沿中轴折叠」的视觉翻转,看清真正的交叉配对关系,最后落成递归与队列两种实现。

先明确问题

给你一棵二叉树的根节点 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 的右孩子都伸向同侧
12233
左边 2 的右孩子与右边 2 的右孩子「相同」—— 但镜像位要求的是右孩子的对侧,落点落空,树不对称。

视觉翻转:沿中轴折叠

现在做一次视觉翻转:不要问「左右子树相同吗」,而是想象这棵树是一张纸,沿着中轴(根节点所在的那条竖线)对折。

对折之后,纸的左半边盖在右半边之上。原来左侧的节点,会落到右侧的某个位置上——落点就是它的「镜像位」。树对称,当且仅当每个节点都正好落在某个同值节点的位置。

看看第三层:左边 2 的右孩子 3(下标 4),折叠后落在哪里?它对应的镜像位,是右边 2 的左孩子的位置(下标 5)。可右边 2 的左孩子是空的——没接住,于是树不对称。

这样,「对称」就从一个抽象的性质,变成了一件可以动手验证的事:把左半边折过去,看每个节点有没有落到它该落的地方。

沿中轴折叠:左半边的每个节点,都要落在右半边的镜像位上
1223443

沿中轴对折这张「树的纸」,看左半边的每个节点落在哪里。

交叉配对:左的左 ↔ 右的右

折叠的落点关系可以用一句话说清:左子树的左孩子 ↔ 右子树的右孩子,左子树的右孩子 ↔ 右子树的左孩子。这是「交叉」的。

把它写成递归:两棵树 p 和 q 对称,当且仅当三件事同时成立——

· p 的值等于 q 的值;

· p 的左子树 与 q 的右子树 对称;

· p 的右子树 与 q 的左子树 对称。

而空节点是递归的天然边界:两个都空 → 对称;一个空一个不空 → 不对称。

模式一旦把问题切成「左右交叉」的小对子,递归就只是把这些对子一个个验证。
交叉配对:一层层地,左的左对右的右,左的右对右的左
1223443
比较对(下标 1, 2):值 2 vs 2,相等 ✓

递归实现

递归版几乎是交叉配对的直接翻译。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:递归

solution.goGo
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 右的左。
同族题目
LC100相同的树(同侧比较)LC104二叉树的最大深度LC226翻转二叉树