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模式,将为解决树结构的层级相关问题打下坚实基础。

相关推荐
@ chen5 分钟前
常见排序算法及其java实现
java·算法·排序算法
带刺的坐椅43 分钟前
SpringBoot2 可以使用 SolonMCP 开发 MCP(江湖救急)
java·spring·ai·solon·mcp
shengjk11 小时前
序列化和反序列化:从理论到实践的全方位指南
java·大数据·开发语言·人工智能·后端·ai编程
jimsten1 小时前
苍穹外卖 - Day02 学习笔记
java·笔记·学习
工业互联网专业1 小时前
基于springboot+vue的医院门诊管理系统
java·vue.js·spring boot·毕业设计·源码·课程设计·医院门诊管理系统
wgc2k1 小时前
Java游戏服务器开发流水账(5)Spring 在游戏开发中的使用简介
java·服务器·游戏
API小爬虫1 小时前
如何用Jsoup库提取商品名称和价格?
java·爬虫
学习中的码虫1 小时前
数据结构中的高级排序算法
数据结构·算法·排序算法
山北雨夜漫步1 小时前
机器学习 Day17 朴素贝叶斯算法-----概率论知识
人工智能·算法·机器学习
Black_Cat_yyds1 小时前
rabbitmq
java·rabbitmq·java-rabbitmq