【LeetCode Hot100 刷题日记 (41/100)】102. 二叉树的层序遍历 —— 树、广度优先搜索(BFS)、队列🌳

33 阅读5分钟

📌 题目链接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]


解题思路(分步拆解)

  1. 初始化

    • 创建结果容器 ret
    • 若根节点为空,直接返回空结果。
  2. BFS 启动

    • 将根节点加入队列。
  3. 逐层处理

    • 记录当前层节点数currentLevelSize = q.size()

    • 创建新层容器ret.push_back({})

    • 遍历当前层所有节点

      • 出队一个节点,将其值加入当前层。
      • 将其非空左右子节点入队(这些属于下一层)。
  4. 循环终止

    • 当队列为空,说明所有层已处理完毕。
  5. 返回结果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!💪

📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!