按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度: 1. 二叉树的中序遍历 - 增加"思路推导"部分,解释递归到迭代的转换 - 详细说明迭代法的每个步骤 - 增加执行过程演示和多种解法 2. 二叉树的最大深度 - 增加"思路推导",对比DFS和BFS - 详细解释递归的基准情况 - 增加多种解法和变体问题 3. 从前序与中序遍历序列构造二叉树 - 详细解释前序和中序的特点 - 增加"思路推导",说明如何分治 - 详细说明切片边界计算 4. 对称二叉树 - 解释镜像对称的定义 - 详细说明递归比较的逻辑 - 增加迭代解法和变体问题 5. 翻转二叉树 - 解释翻转的定义和过程 - 详细说明多值赋值的执行顺序 - 增加多种解法和有趣的故事 6. 路径总和 - 详细解释路径和叶子节点的定义 - 说明为什么使用递减而非累加 - 增加多种解法和变体问题 每个文件都包含: - 完整的示例和边界条件分析 - 详细的算法流程和图解 - 关键细节说明 - 常见错误分析 - 复杂度分析(详细版) - 执行过程演示 - 多种解法 - 变体问题 - 总结 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
14 KiB
二叉树的最大深度 (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
为什么递归这样写?
核心思想:
- 空树深度为0(基准情况)
- 非空树深度 = max(左子树深度, 右子树深度) + 1
- 递归计算左右子树深度
为什么这样思考?
- 分治思想:大问题分解为小问题
- 树的递归定义天然适合递归求解
- 每个节点只需要知道左右子树的深度
解题思路
方法一:深度优先搜索(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
}
总结
核心要点:
- DFS递归: 简洁直观,利用树的递归定义
- BFS迭代: 按层遍历,需要记录层级信息
- 深度公式: max(左子树深度, 右子树深度) + 1
- 基准情况: 空树深度为0
易错点:
- 忘记加1(当前节点)
- BFS忘记记录
levelSize - 空树未处理
方法选择:
- 首选DFS递归(代码简洁)
- 需要层序信息时用BFS
- 避免递归栈溢出时用迭代