LEETCODE 102Medium
二叉树的层序遍历
队列天然按访问顺序吐出节点,但要按层分组,诀窍是在每层开始前先把“当前队列长度”定格下来。
问题拆解
从上到下逐层遍历二叉树,同一层的节点放进同一个子数组。
BFS 用队列做是自然的:根入队,然后不断弹出节点、把它的孩子入队,弹出顺序正好是逐层从左到右。但朴素 BFS 吐出的是一条扁平序列,节点之间没有“层”的边界。给节点附带深度信息可以解决,不过有个更干净的观察:在处理某一层的第一个节点之前,队列里装的恰好是这一层的全部节点——上一层已全部弹出,下一层还没进来。
每轮循环开始时先记下
size = 队列长度,然后恰好弹出 size 个节点,它们就是完整的一层;弹出过程中入队的孩子留给下一轮。
关键在于必须把 size 存进变量:弹出孩子入队时队列长度在实时变化,若循环条件里直接写 queue.size(),层的边界就被冲掉了。
队列按层弹出
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Deque<TreeNode> queue = new ArrayDeque<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size(); // 定格当前层的节点数
List<Integer> level = new ArrayList<>(size);
for (int i = 0; i < size; 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);
}
res.add(level);
}
return res;
}
from collections import deque
def levelOrder(root):
if not root:
return []
res = []
queue = deque([root])
while queue:
size = len(queue) # 定格当前层的节点数
level = []
for _ in range(size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
res.append(level)
return res
func levelOrder(root *TreeNode) [][]int {
if root == nil {
return nil
}
res := [][]int{}
queue := []*TreeNode{root}
for len(queue) > 0 {
size := len(queue) // 定格当前层的节点数
level := make([]int, 0, size)
for i := 0; i < size; 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)
}
}
res = append(res, level)
}
return res
}
use std::cell::RefCell;
use std::collections::VecDeque;
use std::rc::Rc;
pub fn level_order(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Vec<i32>> {
let mut res = Vec::new();
let mut queue = VecDeque::new();
if let Some(node) = root {
queue.push_back(node);
}
while !queue.is_empty() {
let size = queue.len(); // 定格当前层的节点数
let mut level = Vec::with_capacity(size);
for _ in 0..size {
let node = queue.pop_front().unwrap();
let node = node.borrow();
level.push(node.val);
if let Some(left) = node.left.clone() {
queue.push_back(left);
}
if let Some(right) = node.right.clone() {
queue.push_back(right);
}
}
res.push(level);
}
res
}
外层 while 走一次是一层,内层 for 精确弹出 size 个节点——这两层循环的分工就是整个算法。Go 版用切片模拟队列,queue = queue[1:] 弹头部虽不释放底层数组,但树遍历的规模下无妨;Rust 版用 VecDeque,注意孩子指针要 clone() 一份 Rc 再入队,因为 borrow() 出来的引用不能带出作用域。
复杂度
| 指标 | 复杂度 | 原因 |
|---|---|---|
| 时间 | O(n) |
每个节点入队、出队各一次 |
| 空间 | O(n) |
队列最宽时装下一整层,最坏约 n/2 个节点 |
可以迁移的模式
- “先定格 size 再弹 size 个”是 BFS 按层处理的通用骨架,改动收集逻辑就能做锯齿形遍历(103)、右视图(199)、每层最大值(515);
- 需要“逐层”语义时不必给节点挂深度标签,队列在层与层之间的瞬时快照就是分界;
- 求最短步数/最少轮数的题(如 542、994),层数本身就是答案,同一副骨架直接复用。
层序遍历是所有 BFS 变体题的母版,把这个双层循环写熟,剩下的只是换收集方式。