一、题目描述
给你二叉树的根节点 root,返回其节点值的 层序遍历。(即逐层地,从左到右访问所有节点)
示例
| 示例 |
输入 |
输出 |
| 示例1 |
root = [3,9,20,null,null,15,7] |
[[3],[9,20],[15,7]] |
| 示例2 |
root = [1] |
[[1]] |
| 示例3 |
root = [] |
[] |
示例1的树结构:
3
/ \
9 20
/ \
15 7
层序遍历结果:
第1层:[3]
第2层:[9, 20]
第3层:[15, 7]
输出:[[3], [9,20], [15,7]]
提示
- 树中节点数目在范围
[0, 2000] 内
-1000 <= Node.val <= 1000
二、解题思路总览
| 方法 |
核心思想 |
时间复杂度 |
空间复杂度 |
| 迭代(BFS) |
队列 + 分层记录 |
O(n) |
O(n) |
核心思想:
- 使用 队列(FIFO)实现 BFS
- 关键技巧:先记录当前层节点数
n = q.size(),再处理这一层
- 每层处理完后再处理下一层
BFS vs DFS:
- BFS(层序遍历):用队列,按层次一层层往下
- DFS(深度遍历):用栈/递归,先深后浅
三、完整代码
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans; // 1. 存放最终结果
queue<TreeNode*> q; // 2. BFS遍历用队列
if (root != NULL) q.push(root); // 3. 根节点入队
while (!q.empty()) { // 4. 队列不为空时循环
int n = q.size(); // 5. 当前层的节点数(关键!)
vector<int> tmp; // 6. 存放当前层的节点值
// 7. 处理当前层的所有节点
for (int i = 0; i < n; i++) {
TreeNode* node = q.front(); // 取队首节点
q.pop(); // 出队
tmp.push_back(node->val); // 保存节点值
// 8. 将下一层子节点入队
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
ans.push_back(tmp); // 9. 将当前层结果加入答案
}
return ans; // 10. 返回层序遍历结果
}
};
四、算法流程图(ASCII)
树结构与遍历过程
3 ← 第1层
/ \
9 20 ← 第2层
/ \
15 7 ← 第3层
队列变化过程:
初始:q = [3]
第1轮循环(n=1,处理第1层):
出队3,tmp=[3],入队9和20
q = [9, 20]
ans = [[3]]
第2轮循环(n=2,处理第2层):
出队9,tmp=[9]
出队20,tmp=[9,20],入队15和7
q = [15, 7]
ans = [[3], [9,20]]
第3轮循环(n=2,处理第3层):
出队15,tmp=[15]
出队7,tmp=[15,7]
q = []
ans = [[3], [9,20], [15,7]]
队列为空,结束
核心流程图
┌─────────────────────────────────────────────────────────┐
│ 层序遍历主循环 │
├─────────────────────────────────────────────────────────┤
│ │
│ while (!q.empty()) { │
│ int n = q.size(); ← 先记录当前层节点数 │
│ vector<int> tmp; │
│ │
│ for (int i = 0; i < n; i++) { ← 只处理这一层 │
│ TreeNode* node = q.front(); │
│ q.pop(); │
│ tmp.push_back(node->val); │
│ if (node->left) q.push(node->left); │
│ if (node->right) q.push(node->right); │
│ } │
│ │
│ ans.push_back(tmp); ← 当前层处理完再下一层 │
│ } │
│ │
└─────────────────────────────────────────────────────────┘
关键点:
1. 先记录 q.size(),因为入队操作会改变队列大小
2. 只用 for 循环处理当前层,for 结束后才是下一层
3. 每层处理完才 push_back 到 ans
五、逐行解析
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
// ─────────────────────────────────────────
// 第1步:创建结果容器
// 外层vector存储每一层,内层vector存储每层的节点值
// ─────────────────────────────────────────
vector<vector<int>> ans;
// ─────────────────────────────────────────
// 第2步:创建队列
// 队列用于BFS,保证先入队的节点先被处理(按层次顺序)
// ─────────────────────────────────────────
queue<TreeNode*> q;
// ─────────────────────────────────────────
// 第3步:初始化
// 如果根节点不为空,将其入队
// ─────────────────────────────────────────
if (root != NULL) q.push(root);
// ─────────────────────────────────────────
// 第4步:主循环
// 队列为空时说明所有节点都处理完了
// ─────────────────────────────────────────
while (!q.empty()) {
// ─────────────────────────────────────────
// 第5步:记录当前层节点数(关键技巧!)
//
// 必须在 for 循环之前记录
// 因为循环内会入队新节点,改变 q.size()
// ─────────────────────────────────────────
int n = q.size();
// ─────────────────────────────────────────
// 第6步:创建当前层的临时容器
// ─────────────────────────────────────────
vector<int> tmp;
// ─────────────────────────────────────────
// 第7步:处理当前层的所有节点
//
// 注意:只处理 n 个节点(当前层的节点数)
// 处理过程中会把下一层节点入队,但 for 不会处理它们
// ─────────────────────────────────────────
for (int i = 0; i < n; i++) {
TreeNode* node = q.front(); // 取出队首节点
q.pop(); // 队首出队
tmp.push_back(node->val); // 保存节点值
// ─────────────────────────────────────────
// 第8步:将下一层子节点入队
// 左子节点先入队,保证左子节点先被处理
// ─────────────────────────────────────────
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
// ─────────────────────────────────────────
// 第9步:将当前层结果加入答案
// 此时 tmp 中存储了这一层所有节点的值
// ─────────────────────────────────────────
ans.push_back(tmp);
}
// ─────────────────────────────────────────
// 第10步:返回结果
// ─────────────────────────────────────────
return ans;
}
};
六、复杂度分析
时间复杂度
| 分析 |
复杂度 |
| 每个节点入队一次、出队一次 |
O(n) |
推导:
- n 个节点
- 每个节点最多入队一次、出队一次
- 每次入队出队是 O(1) 操作
空间复杂度
推导:
- 最宽的一层最多有 n/2 个节点(完全二叉树最后一层)
- 但队列实际存储的是当前层 + 下一层的节点
- 精确分析:O(n)
七、面试追问 FAQ
| 问题 |
回答 |
为什么先记录 n = q.size()? |
因为 for 循环内会入队新节点改变队列大小 |
为什么不直接在 while 循环内用 q.size()? |
会导致死循环,因为每出队一个就入队两个 |
| BFS 和 DFS 的区别? |
BFS 按层次遍历,用队列;DFS 先深后浅,用栈或递归 |
| 如何区分层数? |
每轮 for 循环对应一层,循环次数就是层数 |
| 如何实现之字形层序遍历(第103题)? |
用双端队列,或加标志位判断奇偶层反转 |
八、相关题目
| 题目 |
难度 |
关键点 |
| 102. 二叉树的层序遍历 |
中等 |
本题 |
| 107. 二叉树的层序遍历 II |
中等 |
从底层往上 |
| 103. 二叉树的锯齿形层序遍历 |
中等 |
之字形 |
| 104. 二叉树的最大深度 |
简单 |
层序遍历统计层数 |
| 111. 二叉树的最小深度 |
简单 |
层序遍历找第一个叶子 |
九、总结
| 对比项 |
说明 |
| 代码行数 |
核心15行 |
| 时间复杂度 |
O(n) |
| 空间复杂度 |
O(n) |
| 核心数据结构 |
队列 |
| 核心技巧 |
先记录 q.size() 再处理当前层 |
核心模板:
while (!q.empty()) {
int n = q.size(); // 先记录当前层节点数
for (int i = 0; i < n; i++) {
TreeNode* node = q.front();
q.pop();
// 处理 node...
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
ans.push_back(tmp);
}