leetcode513.找树左下角的值:递归深度优先搜索中的最左节点追踪之道
一、题目本质与核心诉求解析
在二叉树算法问题中,"找树左下角的值"是一个典型的结合深度与位置判断的问题。题目要求我们找到二叉树中最深层最左边的节点值,这里的"左下角"有两个关键限定:
- 深度优先:必须是深度最大的那一层节点
- 最左优先:在最深层中选择最左边的那个节点
先来看一个示例二叉树:
1/ \2 3/ / \
4 5 6/7
在这个树中,最深层是第3层(根节点为第0层时是第2层),该层最左节点是7,因此正确输出为7。理解这个问题的核心在于如何在递归过程中同时追踪节点深度和左右位置关系,这也是本文要重点解析的内容。
二、递归解法的核心实现与数据结构设计
完整递归代码实现
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val = val; }* TreeNode(int val, TreeNode left, TreeNode right) {* this.val = val;* this.left = left;* this.right = right;* }* }*/
class Solution {private int Deep = -1; // 记录当前最大深度private int res = 0; // 记录最深层最左节点值public int findBottomLeftValue(TreeNode root) {findLeftVal(root, 0); // 从根节点开始,深度初始为0return res;}public void findLeftVal(TreeNode root, int deep) {if (root == null) {return;}// 处理叶子节点:判断是否更新最深层最左节点if (root.left == null && root.right == null) {if (deep > Deep) {res = root.val;Deep = deep;}return;}// 先递归左子树,保证左子树优先访问if (root.left != null) {findLeftVal(root.left, deep + 1);}// 再递归右子树if (root.right != null) {findLeftVal(root.right, deep + 1);}}
}
关键数据结构设计解析
-
Deep变量:
- 作用:记录当前发现的最大深度
- 初始值:-1(因为根节点深度从0开始,确保第一个叶子节点会更新该值)
- 更新时机:当遇到更深的叶子节点时更新
-
res变量:
- 作用:存储最深层最左节点的值
- 更新原则:仅当遇到更深的叶子节点,或者同深度但更左的叶子节点时更新(由于先左后右递归,同深度下左节点必然先访问)
-
deep参数:
- 作用:当前递归层的节点深度
- 传递方式:每次递归进入子节点时深度+1
- 意义:明确当前节点在树中的层级位置
三、核心问题解析:递归中如何判断最深层最左节点
1. 深度优先搜索的顺序控制
本算法采用先左后右的深度优先搜索顺序,这是确保找到"最左"节点的关键:
- 递归调用顺序:先处理左子树,再处理右子树
- 核心逻辑:在同一深度下,左子树的节点会比右子树的节点先被访问
- 效果:当存在多个同深度节点时,左节点会优先被判定为当前最深层最左节点
2. 深度判断与更新机制
if (root.left == null && root.right == null) {if (deep > Deep) {res = root.val;Deep = deep;}
}
- 叶子节点判断:当节点左右子树都为空时,确定为叶子节点
- 深度比较:如果当前叶子节点深度大于已记录的最大深度
Deep
,则更新结果 - 更新策略:
- 更深的叶子节点:直接更新res和Deep
- 同深度的叶子节点:由于先左后右递归,左节点先访问,res不会被右节点覆盖
3. 递归终止与回溯逻辑
- 终止条件:
- 遇到空节点时直接返回(递归边界)
- 处理完叶子节点后返回(不再继续递归)
- 回溯作用:当左子树递归完成后,回溯到父节点,再进入右子树递归,确保左右子树都被遍历
四、递归流程深度模拟:以示例二叉树为例
示例二叉树结构(根节点深度为0):
1(0)/ \2(1) 3(1)/ / \
4(2) 5(2) 6(2)/7(3)
详细递归执行过程
-
初始状态:
Deep = -1
,res = 0
- 调用
findLeftVal(root, 0)
,root为节点1,深度0
-
处理节点1(深度0):
- 非叶子节点,进入左子树调用
findLeftVal(2, 1)
- 非叶子节点,进入左子树调用
-
处理节点2(深度1):
- 非叶子节点,进入左子树调用
findLeftVal(4, 2)
- 非叶子节点,进入左子树调用
-
处理节点4(深度2):
- 是叶子节点(左右子树为空)
- 深度2 > Deep(-1),更新
res=4
,Deep=2
- 返回上一层(节点2)
-
节点2的右子树为空,返回上一层(节点1)
-
节点1进入右子树调用
findLeftVal(3, 1)
-
处理节点3(深度1):
- 非叶子节点,进入左子树调用
findLeftVal(5, 2)
- 非叶子节点,进入左子树调用
-
处理节点5(深度2):
- 非叶子节点,进入左子树调用
findLeftVal(7, 3)
- 非叶子节点,进入左子树调用
-
处理节点7(深度3):
- 是叶子节点
- 深度3 > Deep(2),更新
res=7
,Deep=3
- 返回上一层(节点5)
-
节点5的右子树为空,返回上一层(节点3)
-
节点3进入右子树调用
findLeftVal(6, 2)
-
处理节点6(深度2):
- 是叶子节点
- 深度2 < Deep(3),不更新res
- 返回上一层(节点3)
-
所有递归结束,返回res=7
五、算法复杂度与递归特性分析
1. 时间复杂度分析
- O(n):每个节点仅被访问一次,n为树的节点总数
- 原因:递归过程中对每个节点进行一次深度判断,无重复访问
2. 空间复杂度分析
- O(h):h为树的高度,取决于递归栈的最大深度
- 最坏情况:树退化为链表时,h=n,空间复杂度O(n)
- 平均情况:平衡二叉树h=logn,空间复杂度O(logn)
3. 递归算法的优势与局限
优势 | 局限 |
---|---|
代码逻辑简洁,符合树的递归定义 | 可能因栈溢出无法处理极大树 |
天然支持深度优先搜索顺序控制 | 空间复杂度与树深度相关 |
先左后右的递归顺序自然实现"最左"优先 | 调试相比迭代更困难 |
六、核心技术点总结:递归找最左节点的三大关键
1. 深度优先搜索顺序控制
- 先左后右的递归顺序是实现"最左"优先的核心
- 递归天然保证左子树先于右子树处理,确保同深度下左节点优先被访问
2. 深度追踪与比较机制
- 通过参数传递当前深度,全局变量记录最大深度
- 叶子节点处进行深度比较,确保只保留最深层节点
3. 优先更新策略
- 深度更大时:无条件更新结果
- 深度相同时:由于左子树先处理,结果不会被右节点覆盖
- 实现了"深度优先,同深度左优先"的判定逻辑
七、拓展思考:递归与迭代解法的对比与适用场景
1. 与层序遍历迭代法的对比
方法 | 核心思想 | 时间复杂度 | 空间复杂度 | 优势场景 |
---|---|---|---|---|
递归 | 深度优先+先左后右 | O(n) | O(h) | 代码简洁、树深度较小 |
迭代 | 广度优先+层序处理 | O(n) | O(m)(m为最大层宽度) | 处理极大树、避免栈溢出 |
2. 大厂面试中的策略建议
- 当树深度较小时,递归解法更简洁高效
- 当树可能很深时,应考虑迭代解法避免栈溢出
- 面试中可先给出递归解法,再主动说明迭代优化方向
八、总结:递归解法的核心设计哲学
本递归算法通过"深度优先搜索+先左后右顺序+深度追踪"的组合策略,巧妙解决了找树左下角值的问题。其核心设计哲学包括:
- 深度优先的天然优势:递归天然适合深度优先搜索,能自然追踪节点深度
- 顺序控制的重要性:先左后右的递归顺序是实现"最左"优先的关键
- 状态传递的巧妙设计:通过参数传递深度,全局变量记录结果,实现状态追踪
- 叶子节点的关键作用:仅在叶子节点处判断是否更新结果,提高算法效率
理解这种递归设计思路,不仅能解决本题,还能为类似的二叉树深度与位置相关问题提供通用解法。在实际应用中,可根据树的规模和场景选择递归或迭代实现,灵活应对不同需求。