docs: 改进LeetCode二叉树题目解题思路
按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,20 +1,477 @@
|
||||
# 对称二叉树 (Symmetric Tree)
|
||||
|
||||
LeetCode 101. 简单
|
||||
|
||||
## 题目描述
|
||||
|
||||
给你一个二叉树的根节点 root,检查它是否轴对称。
|
||||
给你一个二叉树的根节点 `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 右的左)
|
||||
```
|
||||
|
||||
## 解题思路
|
||||
|
||||
### 递归比较
|
||||
### 核心思想
|
||||
将问题转化为:**判断两棵树是否互为镜像**
|
||||
|
||||
## Go 代码
|
||||
### 详细算法流程
|
||||
|
||||
**步骤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
|
||||
@@ -22,11 +479,139 @@ func check(left, right *TreeNode) bool {
|
||||
if left == nil || right == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return left.Val == right.Val &&
|
||||
check(left.Left, right.Right) &&
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**原因**: 必须先判断"都为空",再判断"一个为空"
|
||||
|
||||
### 错误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)
|
||||
}
|
||||
```
|
||||
|
||||
**复杂度:** O(n) 时间,O(h) 空间
|
||||
✅ **正确写法**:
|
||||
```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`)
|
||||
- 忘记检查节点值
|
||||
|
||||
**推荐写法**: 递归法(代码简洁,逻辑清晰)
|
||||
|
||||
Reference in New Issue
Block a user