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

752 lines
14 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.
# 二叉树的最大深度 (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
- 避免递归栈溢出时用迭代