Files
interview/16-LeetCode Hot 100/对称二叉树.md
yasinshaw 5c1c974e88 docs: 改进LeetCode二叉树题目解题思路
按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度:

1. 二叉树的中序遍历
   - 增加"思路推导"部分,解释递归到迭代的转换
   - 详细说明迭代法的每个步骤
   - 增加执行过程演示和多种解法

2. 二叉树的最大深度
   - 增加"思路推导",对比DFS和BFS
   - 详细解释递归的基准情况
   - 增加多种解法和变体问题

3. 从前序与中序遍历序列构造二叉树
   - 详细解释前序和中序的特点
   - 增加"思路推导",说明如何分治
   - 详细说明切片边界计算

4. 对称二叉树
   - 解释镜像对称的定义
   - 详细说明递归比较的逻辑
   - 增加迭代解法和变体问题

5. 翻转二叉树
   - 解释翻转的定义和过程
   - 详细说明多值赋值的执行顺序
   - 增加多种解法和有趣的故事

6. 路径总和
   - 详细解释路径和叶子节点的定义
   - 说明为什么使用递减而非累加
   - 增加多种解法和变体问题

每个文件都包含:
- 完整的示例和边界条件分析
- 详细的算法流程和图解
- 关键细节说明
- 常见错误分析
- 复杂度分析(详细版)
- 执行过程演示
- 多种解法
- 变体问题
- 总结

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-08 21:33:57 +08:00

618 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 对称二叉树 (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对应34对应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`
- 忘记检查节点值
**推荐写法**: 递归法(代码简洁,逻辑清晰)