【LeetCode刷题日记】513.二叉树左下角值的三种解法:从常规BFS到DFS的优雅之旅

🔥个人主页:北极的代码(欢迎来访)

🎬作者简介:java后端学习者

❄️个人专栏:苍穹外卖日记SSM框架深入JavaWeb

命运的结局尽可永在,不屈的挑战却不可须臾或缺!

前言:

大家好,我是代码不加冰,又到了每日刷题的时间,我们继续攻克二叉树相关的题目。

摘要:

本文探讨了如何找出二叉树最底层最左边节点的值,提出了两种解法:BFS层序遍历和DFS深度优先搜索。BFS解法通过队列按层遍历,记录每层第一个节点,最终返回最后一层的第一个节点值。DFS解法则利用递归优先遍历左子树,当遇到更深层的叶子节点时更新结果。文章通过示例和详细执行步骤演示了两种方法的实现过程,并分析了其核心机制。最终指出DFS通过先左后右的遍历顺序和深度比较机制,能够准确找到最底层最左边的节点。两种方法各有特点,BFS直观易理解,DFS代码更简洁。

题目背景:

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边节点的值。

假设二叉树中至少有一个节点。

示例 1:

复制代码
输入: root = [2,1,3]
输出: 1

示例 2:

复制代码
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1

题目解析:

我们根据题目来看,要我们找到二叉树最底层,左下角的节点的值,首先我们应该找到二叉树深度最大的那一层,然后找到左子节点。因此就这样整体来看,层序遍历很容易实现,最后一层就是最深的地方,有我们要找的树左下角的值。但递归同样也可以实现,我们都来实现一下。

解法一:BFS(层序遍历)--- 记录每层第一个节点

核心思想

一层一层往下遍历,记录每一层的第一个节点的值。当遍历完所有层后,最后一次记录的值就是最底层最左边的值。

我们先:

  • 创建一个队列,先把根节点放进去

  • 初始化 result 为根节点的值(如果树只有一层,答案就是根节点)


java

复制代码
while (!queue.isEmpty()) {
    int size = queue.size();
  • while 循环:只要队列里还有节点,就继续处理

  • size:当前层的节点数量 (因为队列里放的就是当前层的所有节点)


java

复制代码
    for (int i = 0; i < size; i++) {
        TreeNode node = queue.poll();
  • 遍历当前层的每一个节点

  • queue.poll():从队列头部取出一个节点


java

复制代码
        if (i == 0) {
            result = node.val;
        }
  • 关键 :如果 i == 0,说明这是当前层的第一个节点(最左边)

  • 把这个节点的值更新到 result

  • 随着一层一层往下,result 依次被更新为:第1层最左边 → 第2层最左边 → 第3层最左边 → ... → 最后一层最左边


java

复制代码
        if (node.left != null) queue.offer(node.left);
        if (node.right != null) queue.offer(node.right);
  • 把当前节点的左孩子和右孩子加入队列(成为下一层的节点)

  • 因为队列是先进先出,所以下一层的节点会按从左到右的顺序被处理


java

复制代码
return result;
  • 循环结束后,result 记录的就是最后一层第一个节点的值

举例演示

树:

复制代码
        1
       / \
      2   3
     /   / \
    4   5   6
       /
      7
层数 当前层节点 第一个节点 result更新为
第1层 [1] 1 1
第2层 [2, 3] 2 2
第3层 [4, 5, 6] 4 4
第4层 [7] 7 7

关于while循环和for循环

当我们需要按层处理 时(比如记录每层的第一个节点),就必须知道当前这一层有多少个节点

sizefor 循环开始前就固定了,它记录的是当前层的节点数量。这样:

  • for 循环只处理当前层的 size 个节点

  • 新加入的孩子节点不会在本轮被处理,留到下一轮

  • 所以 i == 0 就能准确知道这是当前层的第一个节点

执行过程可视化

以树为例:

text

复制代码
        1
       / \
      2   3
     /   / \
    4   5   6

第1轮 while 循环(处理第1层)

步骤 操作 队列变化 说明
开始 队列 = [1] - -
int size = queue.size() size = 1 [1] 记录第1层有1个节点
for 第1次迭代 poll() 取出1 [] i=0,记录result=1
加入1的孩子 [2,3] 左2右3
for 结束 - [2,3] 第1层处理完毕

第2轮 while 循环(处理第2层)

步骤 操作 队列变化 说明
开始 队列 = [2,3] - -
int size = queue.size() size = 2 [2,3] 记录第2层有2个节点
for 第1次迭代 poll() 取出2 [3] i=0,记录result=2
加入2的孩子 [3,4] 左4
for 第2次迭代 poll() 取出3 [4] i=1,不记录
加入3的孩子 [4,5,6] 左5右6
for 结束 - [4,5,6] 第2层处理完毕

第3轮 while 循环(处理第3层)

步骤 操作 队列变化 说明
int size = queue.size() size = 3 [4,5,6] 记录第3层有3个节点
for 第1次迭代 poll() 取出4 [5,6] i=0,记录result=4
for 第2次迭代 poll() 取出5 [6] i=1,不记录
for 第3次迭代 poll() 取出6 [] i=2,不记录
while 判断 队列为空 [] 循环结束

最终 result = 4 (最底层最左边的节点)


对比之下,我们还有一个优化的简单方法

在上面中,我们需要利用for循环来确定每层,然后还要处理每层的第一个节点,这样做起来有点麻烦,我们可以灵活利用队列的性质,我们在入队的时候从右往左添加,因此每次先处理的都是右节点,左节点都是后处理的,因此这样循环下去,最后一个节点就是最后一层的左节点,就是我们的答案,我们不需要处理具体是哪一层,也不需要处理第一个节点,比较巧妙。

解法二:BFS(层序遍历)- 从右向左入队
复制代码
        1
       / \
      2   3
     /   / \
    4   5   6
       /
      7

完整执行流程表

轮次 取出前队列 poll()取出 队列变化 加入孩子 取出后队列
1 [1] 1 [] 先加右3,再加左2 [3, 2]
2 [3, 2] 3 [2] 先加右6,再加左5 [2, 6, 5]
3 [2, 6, 5] 2 [6, 5] 先加右(null),再加左4 [6, 5, 4]
4 [6, 5, 4] 6 [5, 4] 右null,左null [5, 4]
5 [5, 4] 5 [4] 先加右7,再加左(null) [4, 7]
6 [4, 7] 4 [7] 右null,左null [7]
7 [7] 7 [] 右null,左null []

循环结束,返回 node.val = 7


解法三:DFS(深度优先搜索)- 优先遍历左子树
java 复制代码
java

class Solution {
    private int maxDepth = -1;
    private int result = 0;
    
    public int findBottomLeftValue(TreeNode root) {
        dfs(root, 0);
        return result;
    }
    
    private void dfs(TreeNode node, int depth) {
        if (node == null) return;
        
        // 到达叶子节点时检查
        if (node.left == null && node.right == null) {
            if (depth > maxDepth) {
                maxDepth = depth;
                result = node.val;
            }
            return;
        }
        
        // 关键:先左后右,确保同深度时优先记录左边的
        dfs(node.left, depth + 1);
        dfs(node.right, depth + 1);
    }
}

递归树结构

复制代码
                    dfs(1, depth=0)
                         |
        ┌────────────────┴────────────────┐
        │                                 │
   dfs(2, 1)                          dfs(3, 1)
        │                                 │
   dfs(4, 2)                    ┌─────────┴─────────┐
        │                        │                   │
   叶子节点4                   dfs(5, 2)           dfs(6, 2)
        │                        │                   │
   更新答案                    dfs(7, 3)            叶子节点6
        │                        │                   │
                          叶子节点7               更新答案?
                          更新答案

逐步执行详解

全局变量初始状态

text

复制代码
maxDepth = -1
result = 0

第1步:dfs(1, 0)

text

复制代码
节点: 1, 深度: 0

检查: 不是叶子节点(有左孩子2和右孩子3)

先递归左子树: dfs(2, 1)

第2步:dfs(2, 1)

text

复制代码
节点: 2, 深度: 1

检查: 不是叶子节点(有左孩子4)

先递归左子树: dfs(4, 2)

第3步:dfs(4, 2) 第一次更新答案

text

复制代码
节点: 4, 深度: 2

检查: 是叶子节点(左右都为null)
    depth(2) > maxDepth(-1) ? ✅ 是
    更新: maxDepth = 2, result = 4
    return (返回上层)

此时状态: maxDepth=2, result=4

text

复制代码
        1
       / \
      2   3
     /   / \
    4 ← 当前  5   6
       /
      7

第4步:回到 dfs(2, 1),继续执行

text

节点2的递归左子树完成

继续执行: dfs(2的右子树, depth+1)

节点2的右孩子为null → dfs(null, 2) 直接返回

dfs(2, 1) 执行完毕,返回到 dfs(1, 0)

第5步:回到 dfs(1, 0),继续执行右子树

text

复制代码
节点1的左子树执行完毕

继续执行右子树: dfs(3, 1)

第6步:dfs(3, 1)

text

复制代码
节点: 3, 深度: 1

检查: 不是叶子节点(有左孩子5和右孩子6)

先递归左子树: dfs(5, 2)

第7步:dfs(5, 2)

text

复制代码
节点: 5, 深度: 2

检查: 不是叶子节点(有左孩子7)

先递归左子树: dfs(7, 3)

第8步:dfs(7, 3) 第二次更新答案

text

复制代码
节点: 7, 深度: 3

检查: 是叶子节点(左右都为null)
    depth(3) > maxDepth(2) ? ✅ 是
    更新: maxDepth = 3, result = 7
    return

此时状态: maxDepth=3, result=7

text

复制代码
        1
       / \
      2   3
     /   / \
    4   5   6
       /
      7 ← 当前

第9步:回到 dfs(5, 2),继续执行

text

复制代码
节点5的左子树完成

继续执行: dfs(5的右子树, depth+1)
       节点5的右孩子为null → dfs(null, 3) 直接返回

dfs(5, 2) 执行完毕,返回到 dfs(3, 1)

第10步:回到 dfs(3, 1),继续执行右子树

text

复制代码
节点3的左子树执行完毕

继续执行右子树: dfs(6, 2)

第11步:dfs(6, 2)

text

复制代码
节点: 6, 深度: 2

检查: 是叶子节点(左右都为null)
    depth(2) > maxDepth(3) ? ❌ 否 (2 > 3 为假)
    不更新答案
    return

此时状态不变: maxDepth=3, result=7

text

复制代码
        1
       / \
      2   3
     /   / \
    4   5   6 ← 当前
       /
      7

第12步:所有递归完成

text

复制代码
dfs(6, 2) 返回 → dfs(3, 1) 返回 → dfs(1, 0) 返回

最终: result = 7 ✅













📈 深度与答案变化追踪

执行顺序 节点 深度 是否叶子 depth > maxDepth? maxDepth变化 result变化
初始 - - - - -1 → -1 0 → 0
1 1 0 - -1 0
2 2 1 - -1 0
3 4 2 2 > -1 ✅ -1 → 2 0 → 4
4 2(右null) - - - 2 4
5 3 1 - 2 4
6 5 2 - 2 4
7 7 3 3 > 2 ✅ 2 → 3 4 → 7
8 5(右null) - - - 3 7
9 6 2 2 > 3 ❌ 3 7
结束 - - - - 3 7

为什么 DFS 能找到最底层最左边的节点

核心机制

机制 作用
先左后右 确保同一深度下,左边的节点先被访问
depth > maxDepth 只有遇到更深的叶子节点时才更新(等于时不更新)
叶子节点才更新 只有最底层的节点才有资格成为答案

题目答案:

java 复制代码
import java.util.*;

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int result = root.val;
        
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                // 每层的第一个节点记录为结果
                if (i == 0) {
                    result = node.val;
                }
                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
        }
        return result;
    }
}
java 复制代码
import java.util.*;

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        TreeNode node = root;
        
        while (!queue.isEmpty()) {
            node = queue.poll();
            // 关键:先右后左,这样最后一个节点就是最底层最左边的
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (node.left != null) {
                queue.offer(node.left);
            }
        }
        return node.val;
    }
}
java 复制代码
import java.util.*;

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        TreeNode node = root;
        
        while (!queue.isEmpty()) {
            node = queue.poll();
            // 关键:先右后左,这样最后一个节点就是最底层最左边的
            if (node.right != null) {
                queue.offer(node.right);
            }
            if (node.left != null) {
                queue.offer(node.left);
            }
        }
        return node.val;
    }
}

结语:如果对你有帮助,请**点赞,关注,收藏,**你的支持就是我最大的鼓励!

相关推荐
雁迟1 小时前
第七章:R 向量用法(最核心数据结构)
开发语言·数据结构·r语言
小哈蒙德2 小时前
基于deepSeekV4Pro(thinking)研究pointPillar的历程
python·算法
兰令水2 小时前
topcode【随机算法题】【2026.5.16打卡-java版本】
java·数据结构·算法
Shan12052 小时前
广度优先搜索之层序遍历
数据结构·算法·宽度优先
SilentSamsara2 小时前
自定义上下文管理器实战:数据库连接池、文件锁与超时控制
开发语言·python·算法·青少年编程
清辞8532 小时前
集合竞价选股程序开发学习
数据结构
吃着火锅x唱着歌2 小时前
LeetCode 503.下一个更大元素II
算法·leetcode·职场和发展
_深海凉_2 小时前
LeetCode热题100-将有序数组转换为二叉搜索树
数据结构·算法·leetcode
KaMeidebaby2 小时前
卡梅德生物技术快报|单克隆抗体人源化 PEG 修饰质控方法体系构建与验证
服务器·前端·数据库·人工智能·算法·百度·新浪微博