【算法题】队列&广度优先搜索

层序遍历(广度优先搜索,BFS)是树结构最核心的遍历方式之一,核心逻辑是按"层"访问节点 ,通过队列实现"先进先出"的层级遍历特性。它不仅能解决基础的层级节点收集问题,还能扩展处理"锯齿形遍历""最大宽度计算""每层最大值查找"等进阶场景,是树类算法的高频考点。本文通过4道经典题目,拆解层序遍历在不同场景下的解题思路与代码实现(涵盖N叉树、二叉树的各类层级问题)。

一、N叉树的层序遍历

题目描述:

给定一个N叉树的根节点 root,返回其节点值的层序遍历(即按从左到右,逐层遍历节点值)。

示例

  • 输入:根节点为1,子节点为[3,2,4],3的子节点为[5,6]
  • 输出:[[1],[3,2,4],[5,6]]

解题思路:

标准层序遍历模板(队列+按层处理):

  1. 初始化队列,若根节点非空则入队。
  2. 循环处理队列:
    • 记录当前层的节点数 sz(队列大小);
    • 遍历当前层的 sz 个节点,收集节点值,同时将每个节点的所有子节点入队;
    • 将当前层的节点值存入结果数组。
  3. 遍历结束后返回结果。

完整代码:

cpp 复制代码
/*
// Definition for a Node.
class Node {
public:
    int val;
    vector<Node*> children;

    Node() {}

    Node(int _val) {
        val = _val;
    }

    Node(int _val, vector<Node*> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>> ret;
        queue<Node*> q;
        if(!root) return ret;
        q.push(root);

        while(!q.empty())
        {
            int sz = q.size(); // 当前层的节点数
            vector<int> tmp;
            while(sz--)
            {
                auto t = q.front();
                q.pop();
                tmp.push_back(t->val);
                // 入队所有子节点
                for(auto& child : t->children)
                    q.push(child);
            }
            ret.push_back(tmp);
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),n 为N叉树的节点总数,每个节点仅入队/出队一次。
  • 空间复杂度:O(n)O(n)O(n),队列最多存储一层的节点(最坏情况为最后一层,节点数接近 nnn)。

二、二叉树的锯齿形层序遍历

题目描述:

给定二叉树的根节点 root,返回其节点值的锯齿形层序遍历(即先从左到右,再从右到左,交替遍历各层)。

示例

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

解题思路:

标准层序遍历 + 奇偶层反转:

  1. 基础逻辑与普通层序遍历一致,新增 level 变量记录当前层数(初始为1)。
  2. 若当前层数为偶数,将该层的节点值数组反转(实现从右到左);
  3. 层数递增,继续处理下一层。

完整代码:

cpp 复制代码
/**
 * 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>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        queue<TreeNode*> q;
        if(!root) return ret;
        q.push(root);
        int level = 1; // 记录当前层数

        while(!q.empty())
        {
            int sz = q.size();
            vector<int> tmp;
            for(int i = 0; i < sz; i++)
            {
                auto t = q.front();
                q.pop();
                tmp.push_back(t->val);
                if(t->left) q.push(t->left);
                if(t->right) q.push(t->right);
            }
            // 偶数层反转
            if(level % 2 == 0) reverse(tmp.begin(), tmp.end());
            ret.push_back(tmp);
            level++;
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),每个节点遍历一次,偶数层反转的总时间为 O(n)O(n)O(n)(所有层节点数之和为 nnn)。
  • 空间复杂度:O(n)O(n)O(n),队列存储层级节点,结果数组存储所有节点值(必要输出,不计入额外复杂度)。

三、二叉树的最大宽度

题目描述:

给定二叉树的根节点 root,返回其最大宽度(宽度定义为:某层最左和最右的非空节点之间的节点数,包括空节点但不包括超出范围的节点)。

示例

  • 输入:root = [1,3,2,5,3,null,9]
  • 输出:4(第三层宽度为4:5,3,null,9)

解题思路:

层序遍历 + 节点编号(模拟完全二叉树的编号规则):

  1. 用"节点+编号"的配对存储队列元素,根节点编号为1;
  2. 左子节点编号 = 父节点编号 × 2,右子节点编号 = 父节点编号 × 2 + 1;
  3. 每层计算最左节点和最右节点的编号差 + 1(即当前层宽度),更新最大宽度;
  4. unsigned int 避免编号溢出(二叉树深度较大时,编号可能超过int范围)。

完整代码:

cpp 复制代码
/**
 * 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:
    int widthOfBinaryTree(TreeNode* root) {
        vector<pair<TreeNode*, unsigned int>> q; // <节点, 编号>
        unsigned int ret = 0;
        if(!root) return 0;
        q.push_back({root, 1});

        while(!q.empty())
        {
            // 计算当前层宽度
            auto& [x1, y1] = q[0];
            auto& [x2, y2] = q.back();
            ret = max(ret, y2 - y1 + 1);

            // 入队下一层节点(仅非空节点)
            vector<pair<TreeNode*, unsigned int>> tmp;
            for(auto& [x, y] : q)
            {
                if(x->left) tmp.push_back({x->left, 2 * y});
                if(x->right) tmp.push_back({x->right, 2 * y + 1});
            }
            q = tmp;
        }

        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),每个节点仅入队/出队一次。
  • 空间复杂度:O(n)O(n)O(n),队列存储"节点+编号"的配对,最坏情况存储一层的所有节点。

四、在每个树行中找最大值

题目描述:

给定二叉树的根节点 root,返回每一层的最大值组成的数组。

示例

  • 输入:root = [1,3,2,5,3,null,9]
  • 输出:[1,3,9]

解题思路:

层序遍历 + 每层最大值维护:

  1. 基础逻辑与普通层序遍历一致;
  2. 遍历当前层的所有节点时,维护一个临时变量 tmp(初始为 INT_MIN),记录当前层的最大值;
  3. 每层遍历结束后,将最大值存入结果数组。

完整代码:

cpp 复制代码
/**
 * 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<int> largestValues(TreeNode* root) {
        vector<int> ret;
        queue<TreeNode*> q;
        if(!root) return {};
        q.push(root);

        while(!q.empty())
        {
            int tmp = INT_MIN; // 初始化为最小值
            int sz = q.size();

            while(sz--)
            {
                auto t = q.front();
                q.pop();
                tmp = max(tmp, t->val); // 更新当前层最大值
                if(t->left) q.push(t->left);
                if(t->right) q.push(t->right);
            }
            ret.push_back(tmp);
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),每个节点仅遍历一次,每层最大值比较为常数级操作。
  • 空间复杂度:O(n)O(n)O(n),队列存储层级节点,结果数组存储每层最大值(必要输出,不计入额外复杂度)。
相关推荐
网域小星球几秒前
C语言从0入门(十)|二维数组详解与矩阵实战
c语言·算法·矩阵·二维数组·数组遍历
澈2076 分钟前
堆排序:高效构建大顶堆实战
数据结构·算法·排序算法
我真不是小鱼28 分钟前
cpp刷题打卡记录27——无重复字符的最长子串 & 找到字符串中所有字母的异位词
数据结构·c++·算法·leetcode
XuecWu333 分钟前
原生多模态颠覆Scaling Law?解读语言“参数需求型”与视觉“数据需求型”核心差异
人工智能·深度学习·算法·计算机视觉·语言模型
We་ct33 分钟前
LeetCode 69. x 的平方根:两种解法详解
前端·javascript·算法·leetcode·typescript·平方
一直不明飞行35 分钟前
C++:string,写法s.find(‘@‘) != s.end()是否有问题
开发语言·c++·算法
Proxy_ZZ01 小时前
打造自己的信道编码工具箱——Turbo、LDPC、极化码三合一
c语言·算法·信息与通信
wayz111 小时前
21天机器学习核心算法学习计划(量化方向)
学习·算法·机器学习
qq. 28040339841 小时前
数据结构引论
前端·数据结构
穿条秋裤到处跑1 小时前
每日一道leetcode(2026.04.09):区间乘法查询后的异或 II
算法·leetcode