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

14 KiB
Raw Blame History

二叉树的最大深度 (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解法 - 层序遍历

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: 定义递归函数

func maxDepth(root *TreeNode) int

步骤2: 确定基准情况(终止条件)

if root == nil {
    return 0  // 空树深度为0
}

关键点: 这是递归的出口,必须明确

步骤3: 递归计算左右子树深度

leftDepth := maxDepth(root.Left)   // 左子树深度
rightDepth := maxDepth(root.Right) // 右子树深度

步骤4: 返回当前树的最大深度

if leftDepth > rightDepth {
    return leftDepth + 1
} else {
    return rightDepth + 1
}

简化写法:

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: 初始化队列和深度

if root == nil {
    return 0
}

queue := []*TreeNode{root}  // 将根节点入队
depth := 0                   // 当前深度

步骤2: 按层遍历

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?

// ❌ 错误写法
return max(leftDepth, rightDepth)  // 忘记加当前节点

// ✅ 正确写法
return max(leftDepth, rightDepth) + 1  // 加上当前节点

原因: 深度是节点数,不是边数

  • 子树深度 = max(左子树深度, 右子树深度)
  • 当前树深度 = 子树深度 + 当前节点(1)

细节2: 为什么要先判断 root == nil?

// BFS方法中
if root == nil {
    return 0  // 空树直接返回,避免初始化空队列
}

原因:

  • 空树没有节点深度为0
  • 如果不判断,会导致 queue = [nil] 的错误状态

细节3: 为什么BFS需要 levelSize?

levelSize := len(queue)
for i := 0; i < levelSize; i++ {
    // 处理当前层
}

原因:

  • 在循环中会不断向队列添加子节点
  • 如果没有 levelSize,会把下一层节点也处理了
  • levelSize 确保只处理当前层的节点

对比:

// ❌ 错误写法
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递归推荐

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
}

简化版:

func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    return max(maxDepth(root.Left), maxDepth(root.Right)) + 1
}

复杂度: O(n) 时间O(h) 空间

方法二BFS迭代

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迭代

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

错误写法:

func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    return max(maxDepth(root.Left), maxDepth(root.Right))  // 忘记+1
}

正确写法:

func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0
    }
    return max(maxDepth(root.Left), maxDepth(root.Right)) + 1  // 加上当前节点
}

原因: 深度包括当前节点需要加1

错误2: BFS忘记记录层级

错误写法:

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++ {
        // 只处理当前层
    }
}

原因: 必须按层处理,不能混在一起

错误3: 空树处理不当

错误写法:

func maxDepth(root *TreeNode) int {
    queue := []*TreeNode{root}  // root可能是nil
    // ...
}

正确写法:

func maxDepth(root *TreeNode) int {
    if root == nil {
        return 0  // 先判断
    }
    queue := []*TreeNode{root}
    // ...
}

原因: 空树需要特殊处理,否则会出错

变体问题

变体1: 最小深度

定义: 根节点到最近叶子节点的最短路径上的节点数

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

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: 计算每个节点的深度

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
  • 避免递归栈溢出时用迭代