# 对称二叉树 (Symmetric Tree) LeetCode 101. 简单 ## 题目描述 给你一个二叉树的根节点 `root`,检查它是否轴对称。 **示例 1:** ``` 输入:root = [1,2,2,3,4,4,3] 输出:true ``` **示例 2:** ``` 输入:root = [1,2,2,null,3,null,3] 输出:false ``` ## 思路推导 ### 什么是轴对称二叉树? **轴对称**: 沿着根节点的中轴线折叠,左右两边完全重合 ``` 对称的树: 不对称的树: 1 1 / \ / \ 2 2 2 2 / \ / \ \ / \ 3 4 4 3 3 3 3 (左右子树不对应) ``` ### 暴力解法分析 **思路**: 比较左子树和右子树是否镜像对称 **观察对称的性质**: 1. 根节点相同(只有一个根) 2. 左子树的左节点 = 右子树的右节点 3. 左子树的右节点 = 右子树的左节点 **图解**: ``` 1 / \ 2 2 <- 左右根节点值相同 / \ / \ 3 4 4 3 <- 3对应3,4对应4 比较规则: - 左子树的左(3) vs 右子树的右(3) - 左子树的右(4) vs 右子树的左(4) ``` **递归思路**: ``` isSymmetric(root): return isMirror(root.left, root.right) isMirror(left, right): 1. 都为空 → true 2. 一个为空 → false 3. 值不同 → false 4. 值相同 → 检查子节点 isMirror(left.left, right.right) && isMirror(left.right, right.left) ``` **时间复杂度**: O(n) - 每个节点访问一次 **空间复杂度**: O(h) - h为树高,递归栈空间 ### 为什么这样思考? **核心思想**: 1. **分治**: 大问题分解为小问题(整棵树对称 → 左右子树镜像) 2. **镜像定义**: 左右对称 = 左子树是右子树的镜像 3. **递归比较**: 从根节点开始,逐层比较对应节点 **为什么是 `left.left` vs `right.right`?** ``` 1 / \ L R / \ / \ LL LR RL RR 对称要求: - LL == RR (左的左 vs 右的右) - LR == RL (左的右 vs 右的左) ``` ## 解题思路 ### 核心思想 将问题转化为:**判断两棵树是否互为镜像** ### 详细算法流程 **步骤1: 定义递归函数** ```go func isMirror(left, right *TreeNode) bool ``` **步骤2: 处理基准情况** **情况1: 两个节点都为空** ```go if left == nil && right == nil { return true // 空树是对称的 } ``` **情况2: 一个节点为空,另一个不为空** ```go if left == nil || right == nil { return false // 不对称 } ``` **关键点**: 必须先判断"都为空",再判断"一个为空" **情况3: 两个节点值不同** ```go if left.Val != right.Val { return false // 值不同,不对称 } ``` **步骤3: 递归检查子节点** ```go // 左的左 vs 右的右 // 左的右 vs 右的左 return isMirror(left.Left, right.Right) && isMirror(left.Right, right.Left) ``` **图解**: ``` 1 / \ 2 2 / \ / \ 3 4 4 3 检查过程: ├─ 2 == 2 ✓ ├─ isMirror(2.left, 2.right) │ ├─ 3 == 3 ✓ │ ├─ isMirror(3.left, 3.right) → isMirror(nil, nil) → true │ └─ isMirror(3.right, 3.left) → isMirror(nil, nil) → true └─ isMirror(2.right, 2.left) ├─ 4 == 4 ✓ ├─ isMirror(4.left, 4.right) → isMirror(nil, nil) → true └─ isMirror(4.right, 4.left) → isMirror(nil, nil) → true ``` ### 关键细节说明 **细节1: 为什么判断顺序很重要?** ```go // ❌ 错误顺序 if left == nil || right == nil { return true // 错误! } // ✅ 正确顺序 if left == nil && right == nil { return true } if left == nil || right == nil { return false } ``` **原因**: - 先判断"都为空"的情况 - 再判断"一个为空"的情况 - 顺序错了会导致逻辑错误 **细节2: 为什么是 `left.Left` vs `right.Right`?** ```go // 镜像对称的对应关系 isMirror(left.Left, right.Right) // 外侧节点 isMirror(left.Right, right.Left) // 内侧节点 ``` **图解**: ``` 1 / \ L R / \ / \ a b c d 对称要求: a == d (左的左 vs 右的右) b == c (左的右 vs 右的左) ``` **细节3: 为什么不需要检查 `left` 和 `right` 的值是否相同?** ```go // 实际上需要检查! if left.Val != right.Val { return false } ``` **原因**: 对称树的对应节点值必须相同 ### 边界条件分析 **边界1: 空树** ``` 输入: root = nil 输出: true 处理: 空树是对称的 ``` **边界2: 只有根节点** ``` 输入: root = [1] 输出: true 处理: - isMirror(nil, nil) - 两个都为空,返回true ``` **边界3: 左子树为空** ``` 输入: 1 \ 2 输出: false 处理: - isMirror(nil, 2) - 一个为空,返回false ``` **边界4: 值不同** ``` 输入: 1 / \ 2 3 输出: false 处理: - 2 != 3 - 返回false ``` **边界5: 结构不同** ``` 输入: 1 / \ 2 2 \ \ 3 3 输出: false 处理: - isMirror(2, 2) ✓ - isMirror(2.right, 2.right) - isMirror(nil, 3) - 一个为空,返回false ``` ### 复杂度分析(详细版) **时间复杂度**: ``` - 每个节点访问一次: O(n) - 每次访问常数操作: O(1) - 总计: O(n) 为什么是O(n)? - 递归遍历所有节点 - 每个节点只比较一次 - 没有重复访问 ``` **空间复杂度**: ``` - 递归栈: O(h) - h为树高 - 最坏情况(链状树): O(n) - 最好情况(完全平衡树): O(log n) - 总计: O(h) ``` ### 执行过程演示 **输入**: ``` 1 / \ 2 2 / \ / \ 3 4 4 3 ``` **执行过程**: ``` 调用 isSymmetric(1): └─ 调用 isMirror(2, 2): ├─ 2 == 2 ✓ ├─ 调用 isMirror(3, 3): │ ├─ 3 == 3 ✓ │ ├─ 调用 isMirror(nil, nil): 返回 true │ └─ 调用 isMirror(nil, nil): 返回 true │ └─ 返回 true && true = true ├─ 调用 isMirror(4, 4): │ ├─ 4 == 4 ✓ │ ├─ 调用 isMirror(nil, nil): 返回 true │ └─ 调用 isMirror(nil, nil): 返回 true │ └─ 返回 true && true = true └─ 返回 true && true = true 最终返回: true ``` **不对称的例子**: ``` 输入: 1 / \ 2 2 \ \ 3 3 执行过程: └─ 调用 isMirror(2, 2): ├─ 2 == 2 ✓ ├─ 调用 isMirror(nil, 3): │ ├─ nil != 3 │ └─ 返回 false └─ 返回 false (短路,不再检查右子树) 最终返回: false ``` ## 代码实现 ### 方法一:递归(推荐) ```go func isSymmetric(root *TreeNode) bool { if root == nil { return true } return check(root.Left, root.Right) } func check(left, right *TreeNode) bool { // 都为空,对称 if left == nil && right == nil { return true } // 一个为空,不对称 if left == nil || right == nil { return false } // 值不同,不对称 if left.Val != right.Val { return false } // 递归检查子节点 return check(left.Left, right.Right) && check(left.Right, right.Left) } ``` **复杂度**: O(n) 时间,O(h) 空间 ### 方法二:迭代(队列) ```go func isSymmetric(root *TreeNode) bool { if root == nil { return true } queue := []*TreeNode{root.Left, root.Right} for len(queue) > 0 { left := queue[0] right := queue[1] queue = queue[2:] if left == nil && right == nil { continue } if left == nil || right == nil { return false } if left.Val != right.Val { return false } // 按镜像顺序入队 queue = append(queue, left.Left, right.Right) queue = append(queue, left.Right, right.Left) } return true } ``` **复杂度**: O(n) 时间,O(n) 空间 ### 方法三:迭代(栈) ```go func isSymmetric(root *TreeNode) bool { if root == nil { return true } stack := []*TreeNode{root.Left, root.Right} for len(stack) > 0 { right := stack[len(stack)-1] stack = stack[:len(stack)-1] left := stack[len(stack)-1] stack = stack[:len(stack)-1] if left == nil && right == nil { continue } if left == nil || right == nil { return false } if left.Val != right.Val { return false } // 按镜像顺序入栈 stack = append(stack, left.Left) stack = append(stack, right.Right) stack = append(stack, left.Right) stack = append(stack, right.Left) } return true } ``` **复杂度**: O(n) 时间,O(n) 空间 ## 常见错误 ### 错误1: 判断顺序错误 ❌ **错误写法**: ```go func check(left, right *TreeNode) bool { if left == nil || right == nil { // 错误! return true } // ... } ``` ✅ **正确写法**: ```go func check(left, right *TreeNode) bool { if left == nil && right == nil { return true } if left == nil || right == nil { return false } // ... } ``` **原因**: 必须先判断"都为空",再判断"一个为空" ### 错误2: 镜像关系错误 ❌ **错误写法**: ```go return check(left.Left, right.Left) && // 错误! check(left.Right, right.Right) ``` ✅ **正确写法**: ```go return check(left.Left, right.Right) && check(left.Right, right.Left) ``` **原因**: 镜像对称是"左的左 vs 右的右","左的右 vs 右的左" ### 错误3: 忘记检查值 ❌ **错误写法**: ```go func check(left, right *TreeNode) bool { if left == nil && right == nil { return true } if left == nil || right == nil { return false } // 忘记检查 left.Val != right.Val return check(left.Left, right.Right) && check(left.Right, right.Left) } ``` ✅ **正确写法**: ```go func check(left, right *TreeNode) bool { if left == nil && right == nil { return true } if left == nil || right == nil { return false } if left.Val != right.Val { return false } return check(left.Left, right.Right) && check(left.Right, right.Left) } ``` **原因**: 对称树的对应节点值必须相同 ## 变体问题 ### 变体1: 判断是否是相同的树 ```go func isSameTree(p *TreeNode, q *TreeNode) bool { if p == nil && q == nil { return true } if p == nil || q == nil { return false } if p.Val != q.Val { return false } return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right) } ``` ### 变体2: 判断是否是轴对称的N叉树 ```go type Node struct { Val int Children []*Node } func isSymmetric(root *Node) bool { if root == nil { return true } return isMirror(root.Children, root.Children) } func isMirror(left, right []*Node) bool { if len(left) != len(right) { return false } for i := 0; i < len(left); i++ { l, r := left[i], right[len(right)-1-i] if !isMirrorNode(l, r) { return false } } return true } func isMirrorNode(a, b *Node) bool { if a == nil && b == nil { return true } if a == nil || b == nil { return false } if a.Val != b.Val { return false } return isMirror(a.Children, b.Children) } ``` ## 总结 **核心要点**: 1. **镜像定义**: 左子树是右子树的镜像 2. **对应关系**: 左的左 vs 右的右,左的右 vs 右的左 3. **基准情况**: 都为空→true,一个为空→false,值不同→false 4. **递归检查**: 从根节点开始,逐层比较对应节点 **易错点**: - 判断顺序错误(先判断"都为空") - 镜像关系错误(应该是 `left.Left` vs `right.Right`) - 忘记检查节点值 **推荐写法**: 递归法(代码简洁,逻辑清晰)