📌 题目链接:102. 二叉树的层序遍历 - 力扣(LeetCode)
🔍 难度:中等 | 🏷️ 标签:树、广度优先搜索(BFS)、队列
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(n)
题目分析
本题要求我们对一棵二叉树进行层序遍历(Level Order Traversal),即从上到下、从左到右逐层访问所有节点,并将每一层的节点值组织成一个子数组,最终返回一个二维数组。
这是典型的广度优先搜索(BFS)应用场景。与深度优先搜索(DFS)不同,BFS 更适合处理“层级”、“最短路径”、“按层操作”等结构化问题。
在面试中,这道题常作为考察候选人对 BFS 基础理解的入门题,但其变体(如锯齿形遍历、层平均值、自底向上遍历等)也频繁出现,因此掌握其核心思想至关重要。
核心算法及代码讲解
✅ 算法选择:广度优先搜索(BFS)
BFS 的本质是逐层扩展,天然契合“层序遍历”的需求。关键在于:如何区分每一层的节点?
🧠 核心技巧:利用队列长度锁定当前层
- 每次进入 while 循环时,队列中的所有节点恰好属于同一层。
- 通过
int currentLevelSize = q.size();获取当前层的节点数量。 - 然后循环
currentLevelSize次,处理完这一层的所有节点,并将它们的子节点(下一层)加入队列。
这个技巧避免了使用额外的哈希表或 pair<node, level> 结构,节省空间且逻辑清晰。
📜 C++ 代码详解(含行注释)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret; // 存储最终结果:每层一个 vector<int>
if (!root) return ret; // 边界:空树直接返回空数组
queue<TreeNode*> q; // BFS 辅助队列
q.push(root); // 根节点入队
while (!q.empty()) { // 只要队列非空,说明还有层未处理
int currentLevelSize = q.size(); // 【关键】当前队列大小 = 当前层节点数
ret.push_back(vector<int>()); // 为当前层预留一个空 vector
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop(); // 取出队首节点
ret.back().push_back(node->val); // 将值加入当前层结果
if (node->left) q.push(node->left); // 左子节点入队(下一层)
if (node->right) q.push(node->right); // 右子节点入队(下一层)
}
// 此时队列中只包含下一层的所有节点,等待下一轮 while 处理
}
return ret;
}
};
💡 注意:
ret.back()是 C++ 中获取 vector 最后一个元素的高效方式,等价于ret[ret.size()-1]。
解题思路(分步拆解)
-
初始化:
- 创建结果容器
ret。 - 若根节点为空,直接返回空结果。
- 创建结果容器
-
BFS 启动:
- 将根节点加入队列。
-
逐层处理:
-
记录当前层节点数:
currentLevelSize = q.size()。 -
创建新层容器:
ret.push_back({})。 -
遍历当前层所有节点:
- 出队一个节点,将其值加入当前层。
- 将其非空左右子节点入队(这些属于下一层)。
-
-
循环终止:
- 当队列为空,说明所有层已处理完毕。
-
返回结果:
ret即为层序遍历结果。
算法分析
| 项目 | 分析 |
|---|---|
| 时间复杂度 | O(n) —— 每个节点入队、出队各一次,共 n 个节点 |
| 空间复杂度 | O(n) —— 队列最多存储一层节点,满二叉树最后一层约 n/2 个节点 |
| 是否稳定 | 是 —— 节点顺序严格按从左到右 |
| 是否原地 | 否 —— 需要额外 O(n) 空间存储结果和队列 |
| 面试考点 | BFS 实现、队列应用、层边界控制、树遍历 |
🎯 面试加分点:
- 能解释为什么
q.size()能代表当前层节点数。- 能对比 DFS + depth 参数实现的递归解法(见文末补充)。
- 能延伸讨论:如何实现“从右到左”层序?如何实现“自底向上”?
代码
✅ C++ 完整可运行代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// Definition for a binary tree node.
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ret;
if (!root) {
return ret;
}
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int currentLevelSize = q.size();
ret.push_back(vector<int>());
for (int i = 1; i <= currentLevelSize; ++i) {
auto node = q.front(); q.pop();
ret.back().push_back(node->val);
if (node->left) q.push(node->left);
if (node->right) q.push(node->right);
}
}
return ret;
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 构建示例1: [3,9,20,null,null,15,7]
TreeNode* root = new TreeNode(3);
root->left = new TreeNode(9);
root->right = new TreeNode(20);
root->right->left = new TreeNode(15);
root->right->right = new TreeNode(7);
Solution sol;
auto result = sol.levelOrder(root);
// 输出: [[3],[9,20],[15,7]]
for (auto& level : result) {
cout << "[";
for (int i = 0; i < level.size(); ++i) {
if (i > 0) cout << ",";
cout << level[i];
}
cout << "]\n";
}
return 0;
}
✅ JavaScript
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[][]}
*/
var levelOrder = function(root) {
const ret = [];
if (!root) {
return ret;
}
const q = [];
q.push(root);
while (q.length !== 0) {
const currentLevelSize = q.length;
ret.push([]);
for (let i = 1; i <= currentLevelSize; ++i) {
const node = q.shift();
ret[ret.length - 1].push(node.val);
if (node.left) q.push(node.left);
if (node.right) q.push(node.right);
}
}
return ret;
};
⚠️ JS 注意:
Array.shift()时间复杂度为 O(n),在大数据量下性能较差。但在 LeetCode 节点数 ≤ 2000 的约束下完全可接受。若追求极致性能,可用双指针模拟队列。
🌟 补充:DFS 递归解法(面试拓展)
虽然 BFS 是本题最自然的解法,但 DFS 也可实现:
class Solution {
private:
vector<vector<int>> ans;
public:
void dfs(TreeNode* root, int depth) {
if (!root) return;
if (depth >= ans.size())
ans.push_back({root->val}); // 新层
else
ans[depth].push_back(root->val); // 现有层
dfs(root->left, depth + 1);
dfs(root->right, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
dfs(root, 0);
return ans;
}
};
✅ DFS vs BFS 对比:
- BFS:空间局部性好,直观体现“层”概念,适合层相关操作。
- DFS:代码简洁,递归隐式使用栈,但需额外 depth 参数管理层级。
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!