目录

题目描述

102. 二叉树的层序遍历

考察公司:美团

image-20230304204355167

image-20230304204404509

题意分析

题目给定一棵二叉树,要求返回一个二维列表:第 i 个内层列表恰好是深度为 i 的所有节点值,且同一层内按从左到右排列。

真正的难点藏在返回值的形态里。如果只要求输出一串扁平序列,那么只需要保证访问顺序满足「浅的节点先访问、同深度左边先访问」就够了;可题目要求按层分组,这就多出一份负担——除了访问顺序,还必须知道每一层在哪里结束。也就是说,光「按正确顺序遍历完所有节点」不够,遍历过程中还要能回答「当前这一层还剩几个节点」或者「当前节点属于第几层」,否则拿到的只是一条连续的节点流,没法切分成一层一层。

题意本身透露了几个信号。分组只由节点的深度决定,与节点值、与树是否平衡、是否完全都无关;同一层内的相对顺序完全由左右孩子的位置决定,所以任何处理方式都必须保证左孩子先于右孩子被收集;另外,层与层之间没有任何重叠,每个节点只属于唯一一层,这说明每个节点只需要被处理一次,不需要回头修补。

边界情形有三类。第一类是空树,此时应返回长度为 0 的二维列表,而不是「含一个空层」的 [[]],这两者在判题时是不同的答案。第二类是只有单侧孩子的斜树,每层只有一个节点,层数等于节点数,层数可以远大于任何一层的宽度。第三类是节点值本身不可信赖——不能假定它们非负或互不相同,因此不能挑一个「特殊值」塞进容器当分层哨兵,那个值完全可能真实出现在树里。

解法:BFS 队列按层遍历

核心思路

队列保证节点按层、从左到右访问。每轮开始先记录当前队列长度,只处理这一批节点;本轮入队的孩子自然留到下一层。

解题步骤

  • 空树直接返回空结果,非空时将根节点入队。
  • 每轮先记录 levelSize = queue.size(),它就是当前层的节点数。
  • 出队 levelSize 个节点,收集节点值,并按先左后右的顺序将非空孩子入队。
  • 将当前层加入结果,直到队列为空。

代码实现

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        if (root == null) {
            return result;
        }

        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<Integer> level = new ArrayList<>(levelSize);

            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);

                if (node.left != null) {
                    queue.offer(node.left);
                }
                if (node.right != null) {
                    queue.offer(node.right);
                }
            }
            result.add(level);
        }
        return result;
    }
}
func levelOrder(root *TreeNode) [][]int {
    result := make([][]int, 0)
    if root == nil {
        return result
    }

    queue := []*TreeNode{root}
    for len(queue) > 0 {
        levelSize := len(queue)
        level := make([]int, 0, levelSize)

        for i := 0; i < levelSize; i++ {
            node := queue[0]
            queue = queue[1:]
            level = append(level, node.Val)

            if node.Left != nil {
                queue = append(queue, node.Left)
            }
            if node.Right != nil {
                queue = append(queue, node.Right)
            }
        }
        result = append(result, level)
    }
    return result
}

复杂度分析

  • 时间复杂度:$O(n)$,每个节点入队、出队各一次。
  • 空间复杂度:$O(w)$,w 为二叉树的最大宽度;不计返回结果。

关键点总结

  • 外层循环每次处理一整层,队列负责维持从左到右的顺序。
  • 必须在处理当前层前固定 levelSize,避免把下一层提前消费。
  • 孩子按先左后右入队,空节点不入队。

易错点总结

  • 在循环条件里动态读取队列长度,会因孩子不断入队而破坏分层。
  • 空树未提前返回,可能把空节点入队并触发空指针。
  • 复用同一个层列表,会让多层结果引用同一份数据。
  • Go 中 make([]int, levelSize) 已创建了指定长度,再 append 会产生多余零值;这里只预分配容量。

相似题目

题目 难度 考察点
103. 二叉树的锯齿形层序遍历 中等 分层骨架不变,多一步「按层号决定本层是否反向」
107. 二叉树的层序遍历 II 中等 层内容完全相同,只是要求自底向上,收尾整体反转或每层插到头部
116. 填充每个节点的下一个右侧节点指针 中等 不收集层内容而是把同层节点串成 next 链,完美二叉树可做到常数空间
117. 填充每个节点的下一个右侧节点指针 II 中等 同样连 next,但树形任意,需要跳过缺失的孩子去找下一个候选
199. 二叉树的右视图 中等 每层只保留最后一个节点,本层列表退化成「取末位元素」
429. N 叉树的层序遍历 中等 孩子从固定的左右两个变成 children 列表,入队处改为遍历子节点数组
513. 找树左下角的值 中等 只要最底层最左值,可改成先右后左入队后取最后一个出队节点,无需存层
515. 在每个树行中找最大值 中等 本层列表换成一个滚动的最大值变量,输出降为一维
637. 二叉树的层平均值 简单 需要层内累加和与层节点数,levelSize 从边界标记变成参与计算的除数
662. 二叉树最大宽度 中等 关心层宽而非层内容,要给节点配下标以把中间的空位也算进宽度
958. 二叉树的完全性检验 中等 反其道而行:孩子不判空照样入队,看第一个空位之后是否还有真实节点
1302. 层数最深叶子节点的和 中等 只要最深一层的和,可以每层覆盖上一层的结果,不必保留全部层
LCR 044. 在每个树行中找最大值 中等 515 的换皮题,与本题的差别只在「每层输出一个最大值而非整层」
LCR 045. 找树左下角的值 中等 513 的换皮题,只取最后一层的第一个节点
LCR 046. 二叉树的右视图 中等 199 的换皮题,只取每层最后一个节点
剑指 Offer 32 - I. 从上到下打印二叉树 中等 输出是扁平一维数组,不需要分组,正好用来对照「快照层长度」的必要性
剑指 Offer 32 - II. 从上到下打印二叉树 II 简单 与本题等价的题面,可以直接套同一份代码
剑指 Offer 32 - III. 从上到下打印二叉树 III 中等 等价于 103 的锯齿输出,奇偶层方向交替
面试题 04.03. 特定深度节点链表 中等 每层结果从列表换成单链表,分层骨架不变、只改本层的收集容器