跳至内容
L102 二叉树的层序遍历

L102 二叉树的层序遍历

题目链接

https://leetcode.cn/problems/binary-tree-level-order-traversal/description/

题目描述

给你二叉树的根节点 root ,返回其节点值的层序遍历(即逐层地,从左到右访问所有节点)。

示例

示例 1

示例 1

输入: root = [3,9,20,null,null,15,7]

输出: [[3],[9,20],[15,7]]

示例 2

输入: root = [1]

输出: [[1]]

示例 3

输入: root = []

输出: []

提示

  • 树中节点数目在范围 [0,2000][0, 2000]
  • 1000Node.val1000-1000 \le \text{Node.val} \le 1000

题解

这道题要求我们按照从上到下、从左到右的顺序,一层一层地遍历二叉树。

这种遍历方式其实就是非常经典的 BFS(Breadth First Search,广度优先搜索)

这一圈一圈向外扩散就行了:先处理当前层的所有结点,再处理下一层。看题目中的示例 1,很好理解,直接先处理一层,然后处理下一层……那这个如何实现?用队列 Queue

我们先把根结点放入队列,之后每次就从队首取一个结点处理,然后把它的左右子节点放到队尾就行了。由于队列先进先出,所以当前层的结点会被优先处理,接着下一层,依次处理。

本题除了要按照 BFS 顺序遍历,还需要最终输出一个 vector<vector<int>>,所以就需要把每一层的结点拼成一个小 vector<int>,最后再拼接到 vector<vector<int>> 返回。

那关键问题就变成了:如何知道当前这一层到底有多少个结点?我一开始是额外维护每一层的结点数量(看后面第一段代码),后来发现其实根本不用这么麻烦——每次开始处理新的一层时,当前队列中的结点数量刚好就是这一层的结点数量

在看代码之前,先简单认识一下这里要用到的 STL 容器 queue

功能示例代码备注
创建queue<int> q;创建一个空队列
入队q.push(1);将元素添加到队尾
出队q.pop();移除队首元素,不返回该元素
访问队首q.front()返回队首元素,不移除
访问队尾q.back()返回队尾元素,不移除
判空q.empty()队列为空返回 true,否则返回 false
元素个数q.size()返回队列中元素的数量

这个是我刚开始的写法,自己维护每一层的结点数量(这个写法不是很好,简单看看就行,不用深入研究):

L102 - 二叉树的层序遍历(维护每层数量) C++
/**
 * 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) // 还需要保留层数信息,试试每一层创建一个队列?太麻烦了,记录每一层的数量吧
        {
            queue <TreeNode*> nodequeue; // 创建结点队列
            vector <int> levelsize; // 维护每一层的结点数量

            nodequeue.push(root); //把头结点放入队列
            levelsize.push_back(1); //把第0层大小设置为1

            vector <vector <int>> ans; //返回结果
            vector <int> ans_level; //每一层的结果
            
            if(root == nullptr) //根结点为空的情况
                return ans;

            int i = 0; //表示层数

            while(!nodequeue.empty()) // 队列不为空就循环,i 表示层数,初始为第0层
            {
                TreeNode *now_node = nodequeue.front(); //取队首
                nodequeue.pop(); //出队
                
                ans_level.push_back(now_node->val); // 当前结点值放入本层结果

                if(now_node->left) //存在左子结点
                {
                    nodequeue.push(now_node->left); //入队

                    //下一层计数
                    if(i == levelsize.size() - 1) //如果不存在下一层
                        levelsize.push_back(1); //创建下一层并初始化为1
                    else
                        levelsize[i+1]++;

                }
                if(now_node->right) //存在右子结点
                {
                    nodequeue.push(now_node->right); //入队

                    //下一层计数
                    if(i == levelsize.size() - 1) //如果不存在下一层
                        levelsize.push_back(1); //创建下一层并初始化为1
                    else
                        levelsize[i+1]++;
                }

                levelsize[i]--; //当前层的数量减一
                if(levelsize[i] == 0) //这一层已经全部处理完了,则可以开启下一层
                {
                    ans.push_back(ans_level); //把这一层的结果放回到输出结果中
                    ans_level.clear();

                    if(i == levelsize.size() - 1)//如果没有下一层了
                        break;

                    else //如果存在下一层
                        i++;
                }
            }

            return ans;
        }
};

这份代码的基本思路没有问题:先创建一个结点队列 nodequeue,同时再使用一个 levelsize 数组记录每一层还有多少个结点没有处理。

每处理一个当前层结点,就执行:levelsize[i]--; 如果发现 levelsize[i] == 0,说明这一层已经全部处理完成,就把当前层结果 ans_level 放入最终结果 ans 中,然后开始处理下一层。

同时,每当发现当前结点存在左子结点或者右子结点时,不仅要把子结点放入队列,还需要维护下一层的结点数量。

这种方法确实可以完成题目,但是写下来会发现:为了知道当前层还有多少个结点,我们额外维护了一个 levelsize,并且每加入一个子结点都要更新它,逻辑明显有点绕。

因此可以用刚刚说的"当前队列中的结点数量刚好就是这一层的结点数量"来完成:

L102 - 二叉树的层序遍历(队列长度即层大小) C++
class Solution 
{
    public:
        vector<vector<int>> levelOrder(TreeNode* root) 
        {
            vector <vector<int>> ans; //返回结果
            if (!root) return ans;  //考虑头结点为空

            queue <TreeNode*> nodequeue; //创建结点队列
            nodequeue.push(root); //放入头结点

            while (!nodequeue.empty()) //结点队列不为空,则遍历队列
            {
                int sz = nodequeue.size(); //sz保存当前队列长度,每一次while都是新的一层,因此也是本层的数量
                vector <int> ans_level; //当前层的结果

                for (int i = 0; i < sz; i++)
                {
                    TreeNode* now_node = nodequeue.front(); //取队首
                    nodequeue.pop(); //出队
                    ans_level.push_back(now_node->val); //放回返回结果

                    if (now_node->left)  nodequeue.push(now_node->left);
                    if (now_node->right) nodequeue.push(now_node->right);
                }
                ans.push_back(ans_level);
            }
            return ans;
        }
};

这份代码就简洁多了。首先创建最终的二维数组 ans 用来保存每一层的遍历结果,然后考虑根结点为空的情况(直接返回空数组即可),接着创建结点队列 nodequeue,并先把根结点放进去。

接下来进入 while 循环,只要队列不为空,就说明还有结点没有处理。这里最关键的就是 int sz = nodequeue.size();,每次刚进入新一轮 while 时,队列中保存的刚好就是当前这一层的所有结点,因此此时的队列长度 sz 就是当前层的结点数量。

for 循环中,每次先通过 front() 取得队首结点,再通过 pop() 将其出队,然后把当前结点的值加入 ans_level。如果这个结点存在左右子结点,就继续将它们加入队尾,等到下一层再进行处理。

for 循环结束,就说明当前层已经全部遍历完成,此时把 ans_level 加入最终结果 ans。然后开始下一轮,现在队列中剩下的刚好就是下一层的全部结点。

于是整个 BFS 的过程就变成了:每轮先记录当前层有多少个结点,把这一层全部出队处理,同时把它们的子结点加入队列,留给下一轮继续处理。一直循环到队列为空,整棵二叉树也就遍历完成了。

这道题我也借助 AI 补充了力扣提交代码之外的本地测试部分,可以直接在自己的编译器中输入数据并运行。完整代码已经整理到 GitHub:https://github.com/C571467648/blog。其他题目也是采用相同的方式整理,建议有需要的话一次性下载使用。如果觉得比较麻烦,或者只想研究题目本身,也可以直接在力扣平台研究 class Solution 部分的代码。

本题讲解就到这里啦,欢迎交流讨论~

交流与指正

本文内容主要来自个人学习与实践总结,受限于个人技术水平,难免存在理解不准确或表述疏漏等错误。 若您发现问题,或愿意就相关内容进一步交流,欢迎通过邮箱 571467648@qq.com 与我联系。感谢您的阅读与指正。