按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
752 lines
14 KiB
Markdown
752 lines
14 KiB
Markdown
# 二叉树的最大深度 (Maximum Depth of Binary Tree)
|
||
|
||
LeetCode 104. 简单
|
||
|
||
## 题目描述
|
||
|
||
给定一个二叉树,找出其最大深度。
|
||
|
||
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
|
||
|
||
**说明**: 叶子节点是指没有子节点的节点。
|
||
|
||
**示例 1:**
|
||
```
|
||
输入:root = [3,9,20,null,null,15,7]
|
||
输出:3
|
||
```
|
||
|
||
**示例 2:**
|
||
```
|
||
输入:root = [1,null,2]
|
||
输出:2
|
||
```
|
||
|
||
**示例 3:**
|
||
```
|
||
输入:root = []
|
||
输出:0
|
||
```
|
||
|
||
## 思路推导
|
||
|
||
### 什么是树的深度?
|
||
|
||
**深度定义**: 从根节点到某个节点的路径上的节点数
|
||
|
||
```
|
||
3 <- 深度1
|
||
/ \
|
||
9 20 <- 深度2
|
||
/ \
|
||
15 7 <- 深度3
|
||
|
||
最大深度 = 3
|
||
```
|
||
|
||
### 暴力解法分析
|
||
|
||
**思路**: 一层一层遍历,数一数有多少层
|
||
|
||
**BFS解法 - 层序遍历**
|
||
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
queue := []*TreeNode{root}
|
||
depth := 0
|
||
|
||
for len(queue) > 0 {
|
||
depth++ // 进入新的一层
|
||
levelSize := len(queue) // 当前层的节点数
|
||
|
||
// 处理当前层的所有节点
|
||
for i := 0; i < levelSize; i++ {
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
|
||
if node.Left != nil {
|
||
queue = append(queue, node.Left)
|
||
}
|
||
if node.Right != nil {
|
||
queue = append(queue, node.Right)
|
||
}
|
||
}
|
||
}
|
||
|
||
return depth
|
||
}
|
||
```
|
||
|
||
**时间复杂度**: O(n) - 每个节点访问一次
|
||
**空间复杂度**: O(w) - w为树的最大宽度(最后一层的节点数)
|
||
|
||
**问题**: 需要额外空间存储队列
|
||
|
||
### 优化思考 - 递归的直观性
|
||
|
||
**核心问题**: 能否更直观地计算深度?
|
||
|
||
**观察**:
|
||
- 树的深度 = max(左子树深度, 右子树深度) + 1
|
||
- 这就是递归的定义!
|
||
|
||
**递归思路**:
|
||
```
|
||
maxDepth(root) =
|
||
0, if root == nil
|
||
max(maxDepth(left), maxDepth(right)) + 1, otherwise
|
||
```
|
||
|
||
### 为什么递归这样写?
|
||
|
||
**核心思想**:
|
||
1. 空树深度为0(基准情况)
|
||
2. 非空树深度 = max(左子树深度, 右子树深度) + 1
|
||
3. 递归计算左右子树深度
|
||
|
||
**为什么这样思考?**
|
||
- 分治思想:大问题分解为小问题
|
||
- 树的递归定义天然适合递归求解
|
||
- 每个节点只需要知道左右子树的深度
|
||
|
||
## 解题思路
|
||
|
||
### 方法一:深度优先搜索(DFS)- 递归
|
||
|
||
### 核心思想
|
||
树的深度由其最深子树决定,递归计算左右子树深度,取最大值加1。
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1: 定义递归函数**
|
||
```go
|
||
func maxDepth(root *TreeNode) int
|
||
```
|
||
|
||
**步骤2: 确定基准情况(终止条件)**
|
||
```go
|
||
if root == nil {
|
||
return 0 // 空树深度为0
|
||
}
|
||
```
|
||
|
||
**关键点**: 这是递归的出口,必须明确
|
||
|
||
**步骤3: 递归计算左右子树深度**
|
||
```go
|
||
leftDepth := maxDepth(root.Left) // 左子树深度
|
||
rightDepth := maxDepth(root.Right) // 右子树深度
|
||
```
|
||
|
||
**步骤4: 返回当前树的最大深度**
|
||
```go
|
||
if leftDepth > rightDepth {
|
||
return leftDepth + 1
|
||
} else {
|
||
return rightDepth + 1
|
||
}
|
||
```
|
||
|
||
**简化写法**:
|
||
```go
|
||
return max(leftDepth, rightDepth) + 1
|
||
```
|
||
|
||
**图解**:
|
||
```
|
||
3 maxDepth(3)
|
||
/ \ = max(maxDepth(9), maxDepth(20)) + 1
|
||
9 20 = max(1, 2) + 1
|
||
/ \ = 3
|
||
15 7
|
||
|
||
递归过程:
|
||
maxDepth(9) = max(maxDepth(nil), maxDepth(nil)) + 1 = 1
|
||
maxDepth(20) = max(maxDepth(15), maxDepth(7)) + 1
|
||
= max(1, 1) + 1 = 2
|
||
maxDepth(15) = 1
|
||
maxDepth(7) = 1
|
||
```
|
||
|
||
### 方法二:广度优先搜索(BFS)- 迭代
|
||
|
||
### 核心思想
|
||
按层遍历树,每遍历一层深度加1,直到遍历完所有节点。
|
||
|
||
### 详细算法流程
|
||
|
||
**步骤1: 初始化队列和深度**
|
||
```go
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
queue := []*TreeNode{root} // 将根节点入队
|
||
depth := 0 // 当前深度
|
||
```
|
||
|
||
**步骤2: 按层遍历**
|
||
```go
|
||
for len(queue) > 0 {
|
||
depth++ // 进入新的一层
|
||
levelSize := len(queue) // 记录当前层节点数
|
||
|
||
// 处理当前层的所有节点
|
||
for i := 0; i < levelSize; i++ {
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
|
||
// 将下一层节点入队
|
||
if node.Left != nil {
|
||
queue = append(queue, node.Left)
|
||
}
|
||
if node.Right != nil {
|
||
queue = append(queue, node.Right)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**关键点**:
|
||
- `levelSize` 很重要,确保只处理当前层的节点
|
||
- 处理完当前层后,队列中全是下一层的节点
|
||
|
||
**图解**:
|
||
```
|
||
初始状态:
|
||
queue: [3]
|
||
depth: 0
|
||
|
||
第1层:
|
||
queue: [3] depth++ → depth = 1
|
||
处理3:
|
||
queue: [9, 20]
|
||
|
||
第2层:
|
||
queue: [9, 20] depth++ → depth = 2
|
||
处理9, 20:
|
||
queue: [15, 7]
|
||
|
||
第3层:
|
||
queue: [15, 7] depth++ → depth = 3
|
||
处理15, 7:
|
||
queue: []
|
||
|
||
退出循环,返回 depth = 3
|
||
```
|
||
|
||
### 关键细节说明
|
||
|
||
**细节1: 为什么是 `leftDepth + 1` 而不是 `leftDepth`?**
|
||
|
||
```go
|
||
// ❌ 错误写法
|
||
return max(leftDepth, rightDepth) // 忘记加当前节点
|
||
|
||
// ✅ 正确写法
|
||
return max(leftDepth, rightDepth) + 1 // 加上当前节点
|
||
```
|
||
|
||
**原因**: 深度是节点数,不是边数
|
||
- 子树深度 = max(左子树深度, 右子树深度)
|
||
- 当前树深度 = 子树深度 + 当前节点(1)
|
||
|
||
**细节2: 为什么要先判断 `root == nil`?**
|
||
|
||
```go
|
||
// BFS方法中
|
||
if root == nil {
|
||
return 0 // 空树直接返回,避免初始化空队列
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- 空树没有节点,深度为0
|
||
- 如果不判断,会导致 `queue = [nil]` 的错误状态
|
||
|
||
**细节3: 为什么BFS需要 `levelSize`?**
|
||
|
||
```go
|
||
levelSize := len(queue)
|
||
for i := 0; i < levelSize; i++ {
|
||
// 处理当前层
|
||
}
|
||
```
|
||
|
||
**原因**:
|
||
- 在循环中会不断向队列添加子节点
|
||
- 如果没有 `levelSize`,会把下一层节点也处理了
|
||
- `levelSize` 确保只处理当前层的节点
|
||
|
||
**对比**:
|
||
```go
|
||
// ❌ 错误写法
|
||
for len(queue) > 0 { // 会把所有层混在一起处理
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
depth++ // depth会变成节点数而非层数
|
||
}
|
||
|
||
// ✅ 正确写法
|
||
for len(queue) > 0 {
|
||
depth++
|
||
levelSize := len(queue)
|
||
for i := 0; i < levelSize; i++ {
|
||
// 只处理当前层
|
||
}
|
||
}
|
||
```
|
||
|
||
### 边界条件分析
|
||
|
||
**边界1: 空树**
|
||
```
|
||
输入: root = nil
|
||
输出: 0
|
||
处理:
|
||
- DFS: 直接返回0
|
||
- BFS: 判断root==nil,返回0
|
||
```
|
||
|
||
**边界2: 只有根节点**
|
||
```
|
||
输入: root = [1]
|
||
输出: 1
|
||
处理:
|
||
- DFS: max(maxDepth(nil), maxDepth(nil)) + 1 = 1
|
||
- BFS: 初始queue=[1], depth=1, 处理后queue=[], 退出
|
||
```
|
||
|
||
**边界3: 链状树(所有节点只有左子树)**
|
||
```
|
||
输入:
|
||
1
|
||
/
|
||
2
|
||
\
|
||
3
|
||
|
||
输出: 3
|
||
处理: DFS会递归到最深处,逐层返回
|
||
```
|
||
|
||
**边界4: 完全二叉树**
|
||
```
|
||
输入:
|
||
1
|
||
/ \
|
||
2 3
|
||
/ \ / \
|
||
4 5 6 7
|
||
|
||
输出: 3
|
||
处理: 左右子树深度相同
|
||
```
|
||
|
||
### 复杂度分析(详细版)
|
||
|
||
#### DFS递归方法
|
||
|
||
**时间复杂度**:
|
||
```
|
||
- 每个节点访问一次: O(n)
|
||
- 每次访问只做常数操作: O(1)
|
||
- 总计: O(n)
|
||
|
||
为什么是O(n)?
|
||
- 递归函数被调用n次(每个节点一次)
|
||
- 每次调用只计算max和+1
|
||
- 没有重复计算
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
- 递归栈空间: O(h) - h为树高
|
||
- 最坏情况(链状树): O(n)
|
||
- 最好情况(完全平衡树): O(log n)
|
||
- 总计: O(h)
|
||
|
||
为什么是O(h)而不是O(n)?
|
||
- 递归深度 = 树的高度
|
||
- 同一时刻栈中最多有h个栈帧
|
||
- 不是所有节点同时在栈中
|
||
```
|
||
|
||
#### BFS迭代方法
|
||
|
||
**时间复杂度**:
|
||
```
|
||
- 每个节点入队出队一次: O(n)
|
||
- 每个节点处理常数次: O(1)
|
||
- 总计: O(n)
|
||
```
|
||
|
||
**空间复杂度**:
|
||
```
|
||
- 队列空间: O(w) - w为树的最大宽度
|
||
- 最坏情况(完全二叉树最后一层): O(n/2) = O(n)
|
||
- 最好情况(链状树): O(1)
|
||
- 总计: O(w)
|
||
```
|
||
|
||
### 执行过程演示
|
||
|
||
**输入**:
|
||
```
|
||
3
|
||
/ \
|
||
9 20
|
||
/ \
|
||
15 7
|
||
```
|
||
|
||
#### DFS递归执行过程
|
||
|
||
```
|
||
调用 maxDepth(3):
|
||
├─ 调用 maxDepth(9):
|
||
│ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ └─ 返回 max(0, 0) + 1 = 1
|
||
├─ 调用 maxDepth(20):
|
||
│ ├─ 调用 maxDepth(15):
|
||
│ │ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ │ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ │ └─ 返回 max(0, 0) + 1 = 1
|
||
│ ├─ 调用 maxDepth(7):
|
||
│ │ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ │ ├─ 调用 maxDepth(nil): 返回 0
|
||
│ │ └─ 返回 max(0, 0) + 1 = 1
|
||
│ └─ 返回 max(1, 1) + 1 = 2
|
||
└─ 返回 max(1, 2) + 1 = 3
|
||
|
||
最终返回: 3
|
||
```
|
||
|
||
#### BFS迭代执行过程
|
||
|
||
```
|
||
初始:
|
||
queue = [3], depth = 0
|
||
|
||
处理第1层:
|
||
depth = 1
|
||
levelSize = 1
|
||
处理节点3:
|
||
左孩子9入队
|
||
右孩子20入队
|
||
queue = [9, 20]
|
||
|
||
处理第2层:
|
||
depth = 2
|
||
levelSize = 2
|
||
处理节点9:
|
||
无孩子
|
||
处理节点20:
|
||
左孩子15入队
|
||
右孩子7入队
|
||
queue = [15, 7]
|
||
|
||
处理第3层:
|
||
depth = 3
|
||
levelSize = 2
|
||
处理节点15:
|
||
无孩子
|
||
处理节点7:
|
||
无孩子
|
||
queue = []
|
||
|
||
退出循环
|
||
返回 depth = 3
|
||
```
|
||
|
||
## 代码实现
|
||
|
||
### 方法一:DFS递归(推荐)
|
||
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
leftDepth := maxDepth(root.Left)
|
||
rightDepth := maxDepth(root.Right)
|
||
|
||
if leftDepth > rightDepth {
|
||
return leftDepth + 1
|
||
}
|
||
return rightDepth + 1
|
||
}
|
||
```
|
||
|
||
**简化版**:
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
return max(maxDepth(root.Left), maxDepth(root.Right)) + 1
|
||
}
|
||
```
|
||
|
||
**复杂度**: O(n) 时间,O(h) 空间
|
||
|
||
### 方法二:BFS迭代
|
||
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
queue := []*TreeNode{root}
|
||
depth := 0
|
||
|
||
for len(queue) > 0 {
|
||
depth++
|
||
levelSize := len(queue)
|
||
|
||
for i := 0; i < levelSize; i++ {
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
|
||
if node.Left != nil {
|
||
queue = append(queue, node.Left)
|
||
}
|
||
if node.Right != nil {
|
||
queue = append(queue, node.Right)
|
||
}
|
||
}
|
||
}
|
||
|
||
return depth
|
||
}
|
||
```
|
||
|
||
**复杂度**: O(n) 时间,O(w) 空间
|
||
|
||
### 方法三:DFS迭代(栈)
|
||
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
type NodeWithDepth struct {
|
||
node *TreeNode
|
||
depth int
|
||
}
|
||
|
||
stack := []NodeWithDepth{{root, 1}}
|
||
maxDepthVal := 0
|
||
|
||
for len(stack) > 0 {
|
||
current := stack[len(stack)-1]
|
||
stack = stack[:len(stack)-1]
|
||
|
||
if current.node != nil {
|
||
maxDepthVal = max(maxDepthVal, current.depth)
|
||
stack = append(stack, NodeWithDepth{current.node.Left, current.depth + 1})
|
||
stack = append(stack, NodeWithDepth{current.node.Right, current.depth + 1})
|
||
}
|
||
}
|
||
|
||
return maxDepthVal
|
||
}
|
||
```
|
||
|
||
**复杂度**: O(n) 时间,O(h) 空间
|
||
|
||
## 常见错误
|
||
|
||
### 错误1: 忘记加1
|
||
|
||
❌ **错误写法**:
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
return max(maxDepth(root.Left), maxDepth(root.Right)) // 忘记+1
|
||
}
|
||
```
|
||
|
||
✅ **正确写法**:
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
return max(maxDepth(root.Left), maxDepth(root.Right)) + 1 // 加上当前节点
|
||
}
|
||
```
|
||
|
||
**原因**: 深度包括当前节点,需要加1
|
||
|
||
### 错误2: BFS忘记记录层级
|
||
|
||
❌ **错误写法**:
|
||
```go
|
||
for len(queue) > 0 {
|
||
node := queue[0]
|
||
queue = queue[1:]
|
||
depth++ // 错误:这样depth等于节点数而非层数
|
||
// ...
|
||
}
|
||
```
|
||
|
||
✅ **正确写法**:
|
||
```go
|
||
for len(queue) > 0 {
|
||
depth++
|
||
levelSize := len(queue) // 记录当前层节点数
|
||
for i := 0; i < levelSize; i++ {
|
||
// 只处理当前层
|
||
}
|
||
}
|
||
```
|
||
|
||
**原因**: 必须按层处理,不能混在一起
|
||
|
||
### 错误3: 空树处理不当
|
||
|
||
❌ **错误写法**:
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
queue := []*TreeNode{root} // root可能是nil
|
||
// ...
|
||
}
|
||
```
|
||
|
||
✅ **正确写法**:
|
||
```go
|
||
func maxDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0 // 先判断
|
||
}
|
||
queue := []*TreeNode{root}
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**原因**: 空树需要特殊处理,否则会出错
|
||
|
||
## 变体问题
|
||
|
||
### 变体1: 最小深度
|
||
|
||
**定义**: 根节点到最近叶子节点的最短路径上的节点数
|
||
|
||
```go
|
||
func minDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
// 叶子节点
|
||
if root.Left == nil && root.Right == nil {
|
||
return 1
|
||
}
|
||
|
||
// 如果某个子树为空,不考虑该子树
|
||
if root.Left == nil {
|
||
return minDepth(root.Right) + 1
|
||
}
|
||
if root.Right == nil {
|
||
return minDepth(root.Left) + 1
|
||
}
|
||
|
||
return min(minDepth(root.Left), minDepth(root.Right)) + 1
|
||
}
|
||
```
|
||
|
||
### 变体2: 判断是否为平衡二叉树
|
||
|
||
**定义**: 任意节点左右子树深度差不超过1
|
||
|
||
```go
|
||
func isBalanced(root *TreeNode) bool {
|
||
return checkDepth(root) != -1
|
||
}
|
||
|
||
func checkDepth(root *TreeNode) int {
|
||
if root == nil {
|
||
return 0
|
||
}
|
||
|
||
leftDepth := checkDepth(root.Left)
|
||
if leftDepth == -1 {
|
||
return -1 // 左子树不平衡
|
||
}
|
||
|
||
rightDepth := checkDepth(root.Right)
|
||
if rightDepth == -1 {
|
||
return -1 // 右子树不平衡
|
||
}
|
||
|
||
if abs(leftDepth - rightDepth) > 1 {
|
||
return -1 // 当前节点不平衡
|
||
}
|
||
|
||
return max(leftDepth, rightDepth) + 1
|
||
}
|
||
```
|
||
|
||
### 变体3: 计算每个节点的深度
|
||
|
||
```go
|
||
type NodeWithDepth struct {
|
||
node *TreeNode
|
||
depth int
|
||
}
|
||
|
||
func nodesWithDepth(root *TreeNode) []*NodeWithDepth {
|
||
if root == nil {
|
||
return []*NodeWithDepth{}
|
||
}
|
||
|
||
result := []*NodeWithDepth{}
|
||
stack := []*NodeWithDepth{{root, 1}}
|
||
|
||
for len(stack) > 0 {
|
||
current := stack[len(stack)-1]
|
||
stack = stack[:len(stack)-1]
|
||
|
||
result = append(result, current)
|
||
|
||
if current.node.Right != nil {
|
||
stack = append(stack, &NodeWithDepth{current.node.Right, current.depth + 1})
|
||
}
|
||
if current.node.Left != nil {
|
||
stack = append(stack, &NodeWithDepth{current.node.Left, current.depth + 1})
|
||
}
|
||
}
|
||
|
||
return result
|
||
}
|
||
```
|
||
|
||
## 总结
|
||
|
||
**核心要点**:
|
||
1. **DFS递归**: 简洁直观,利用树的递归定义
|
||
2. **BFS迭代**: 按层遍历,需要记录层级信息
|
||
3. **深度公式**: max(左子树深度, 右子树深度) + 1
|
||
4. **基准情况**: 空树深度为0
|
||
|
||
**易错点**:
|
||
- 忘记加1(当前节点)
|
||
- BFS忘记记录 `levelSize`
|
||
- 空树未处理
|
||
|
||
**方法选择**:
|
||
- 首选DFS递归(代码简洁)
|
||
- 需要层序信息时用BFS
|
||
- 避免递归栈溢出时用迭代
|