102. 二叉树的层序遍历详解:队列操作与层级分组的核心逻辑

二叉树的 层序遍历 是广度优先搜索(BFS)的经典应用,其核心是按层级逐层访问节点,确保每一层的节点按从左到右的顺序被处理。本文将结合代码实现与队列操作模拟,深入解析层序遍历的两大核心难点,并通过具体案例演示层级分组的关键逻辑。

一、题目描述

给定一个二叉树的根节点 root,返回其节点值的 层序遍历 结果(即逐层遍历,从左到右访问所有节点)。

例如,输入二叉树:

复制代码
    3
   / \
  9  20
    /  \
   15   7

输出结果为:
[[3], [9,20], [15,7]]

二、核心难点分析

难点1:出入队逻辑------如何区分当前层与下一层节点?

层序遍历的本质是通过队列实现 层级隔离,确保每一步处理的是同一层的节点,而子节点被暂存到队列中,作为下一层的待处理节点。

  • 入队规则
    每次处理当前层节点时,将其左右子节点按顺序入队(先左后右),保证下一层节点在队列中按从左到右的顺序排列。
  • 出队规则
    每次循环开始时,队列中存储的是当前层的所有节点,通过固定次数的出队操作(次数等于当前层节点数),确保不会提前处理下一层节点。

类比理解

队列就像一个"层级传送带":

  1. 第一层节点(根节点)入队后,传送带启动,每次取出当前层所有节点(出队),并将它们的子节点(下一层)按顺序放入传送带尾部(入队)。
  2. 传送带每"运行一轮",就处理完一层节点,且下一层节点已在传送带中等待,确保层级不会混淆。

难点2:循环压入结果数组的逻辑------如何避免跨层数据污染?

层序遍历需要将每一层的节点值单独收集到一个列表中,关键在于 通过当前层节点数控制循环范围

  1. 在每层循环开始时,记录队列的大小 levelSize,这个值就是当前层的节点总数(因为此时队列中仅包含当前层节点)。
  2. 内层循环强制执行 levelSize 次出队操作,确保只处理当前层的节点,即使在循环中向队列添加了下一层节点(子节点入队),也不会影响内层循环的次数(因为 levelSize 是固定的)。

反例说明

如果不记录 levelSize,直接通过 !queue.isEmpty() 控制内层循环,会导致下一层节点被提前处理,最终结果中不同层的节点会混合在一起(例如第二层节点和第三层节点被收集到同一个列表中)。

三、解题思路分步解析

步骤1:初始化队列与结果列表

java 复制代码
List<List<Integer>> res = new ArrayList<>();  // 存储最终结果,每一层是一个子列表
Deque<TreeNode> queue = new ArrayDeque<>();   // 使用双端队列实现队列操作(Java中推荐用ArrayDeque)
if (root != null) {
    queue.offer(root);  // 根节点入队,作为第一层唯一的节点
}
  • 为什么用Deque而非Queue?
    Deque(双端队列)提供了更高效的 offer()poll() 操作,且在Java中 PriorityQueue 不适合此处场景(层序遍历需要严格的FIFO顺序,无需优先级)。

步骤2:逐层处理节点(核心逻辑)

java 复制代码
while (!queue.isEmpty()) {
    int levelSize = queue.size();  // 关键!记录当前层的节点数,此时队列中只有当前层节点
    List<Integer> levelList = new ArrayList<>();  // 临时存储当前层的节点值
    
    // 内层循环:处理当前层的所有节点(严格执行levelSize次)
    for (int i = 0; i < levelSize; i++) {
        TreeNode node = queue.poll();  // 出队当前层节点(从队首取出,保证左到右顺序)
        levelList.add(node.val);       // 记录节点值
        
        // 子节点入队(先左后右,保证下一层节点在队列中的顺序)
        if (node.left != null) queue.offer(node.left);  // 左子节点入队(队尾)
        if (node.right != null) queue.offer(node.right);  // 右子节点入队(队尾)
    }
    
    res.add(levelList);  // 当前层处理完毕,结果加入最终列表
}
关键细节解析:
  1. levelSize = queue.size() 的作用

    • 在进入内层循环前,队列中存储的是当前层的所有节点(例如第一层只有根节点,第二层是根节点的左右子节点)。
    • 这个值确保了内层循环只会处理当前层的节点,而后续入队的子节点(下一层)会被外层循环的下一次迭代处理。
  2. 子节点入队的顺序

    • 先左子节点后右子节点,保证下一层节点在队列中按从左到右的顺序排列,从而在内层循环中按顺序处理(左到右访问)。

四、出入队模拟示例(以样例二叉树为例)

二叉树结构:

复制代码
    3
   / \
  9  20
    /  \
   15   7

队列变化与结果收集过程如下:

处理层级 队列内容(队首→队尾) 内层循环操作 临时列表 levelList 结果 res
第1层(根层) [3] 弹出3,左子节点9和右子节点20入队 [3] [[3]]
第2层 [9, 20] 弹出9(无子节点),弹出20(左15、右7入队) [9, 20] [[3], [9, 20]]
第3层 [15, 7] 弹出15(无子节点),弹出7(无子节点) [15, 7] [[3], [9, 20], [15, 7]]
  • 关键观察
    每次外层循环处理时,队列中始终只包含当前层的节点,子节点入队后成为下一次外层循环的处理对象,确保层级严格分离。

五、完整代码与复杂度分析

完整代码

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

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 {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();
        Deque<TreeNode> queue = new ArrayDeque<>();
        if (root != null) {
            queue.offer(root);
        }
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> levelList = new ArrayList<>();
            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                levelList.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            res.add(levelList);
        }
        return res;
    }
}

复杂度分析

  • 时间复杂度:O(n),每个节点恰好入队和出队一次,内层循环总次数等于节点总数n。
  • 空间复杂度:O(n),最坏情况下队列存储最后一层的所有节点(例如完全二叉树的最后一层有n/2个节点)。

六、扩展问题与变种题型

掌握层序遍历的核心逻辑后,可以轻松解决以下变种问题:

  1. 自底向上的层序遍历(LeetCode 107)
    • 只需将收集到的每层结果逆序添加到最终列表中。
  2. 锯齿形层序遍历(LeetCode 103)
    • 偶数层(从0开始计数)将临时列表反转,实现左右交替的访问顺序。
  3. 二叉树右视图(LeetCode 199)
    • 每层处理时,将最后一个节点的值加入结果(利用队列的FIFO特性,最后出队的节点是当前层最右边的节点)。

七、总结

层序遍历的核心是通过 队列的FIFO特性实现层级隔离 ,并通过 固定层节点数的循环 确保每层数据准确收集。理解这两大难点后,不仅能轻松解决基础层序遍历问题,还能快速应对各种变种题型。

  • 出入队逻辑:当前层节点出队时,子节点按顺序入队,作为下一层的待处理节点,保证层级有序。
  • 结果收集逻辑:通过记录当前层节点数,内层循环严格处理固定次数,避免跨层数据污染。

掌握这一经典BFS模式,将为解决树结构的层级相关问题打下坚实基础。

相关推荐
卷福同学3 小时前
不用服务器,不用配环境,我10分钟上线了一个AI Agent
人工智能·后端·算法
码兄科技5 小时前
Java AI智能体开发实战:从零构建智能对话系统指南
java·开发语言·人工智能
折哥的程序人生 · 物流技术专研5 小时前
第8篇:六大设计原则在工厂模式中的体现
java·设计模式·设计原则·工厂模式·编程进阶·扩充系列
问君能有几多愁~5 小时前
C++ 数据结构复习笔记
数据结构·c++·笔记
至乐活着6 小时前
深入解析跳表SkipList:原理、实现与性能优化实战
数据结构·算法·跳表·skiplist·java实现
小明bishe186 小时前
计算机毕业设计之基于JAVA的植物科普网站
java·spring boot·spring·架构·课程设计
r_oo_ki_e_6 小时前
Java Map 集合学习笔记
java·笔记·学习
SL-staff6 小时前
智慧园区2000+设备统一管理:JVS-IoT如何降低运维成本40%
java·开发语言·物联网·智慧园区·设备管理·运维优化·jvs-iot
Jerry6 小时前
LeetCode 383. 赎金信
算法
ai产品老杨7 小时前
H264 H265视频分析常见问题和排查清单
人工智能·算法·音视频