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