【优选算法篇】队列与宽度优先搜索(BFS)——层层递进的视野

文章目录

    • [BFS 的奥义:涟漪式扩散,掌控全局](#BFS 的奥义:涟漪式扩散,掌控全局)
    • [一、 N 叉树的层序遍历:打破维度的墙 (Medium)](#一、 N 叉树的层序遍历:打破维度的墙 (Medium))
      • [1.1 题目描述](#1.1 题目描述)
      • [1.2 算法思路:队列控制层级](#1.2 算法思路:队列控制层级)
      • [1.3 C++ 代码实战](#1.3 C++ 代码实战)
    • [二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium)](#二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium))
      • [2.1 题目描述](#2.1 题目描述)
      • [2.2 算法思路:偶数层大反转](#2.2 算法思路:偶数层大反转)
      • [2.3 C++ 代码实战](#2.3 C++ 代码实战)
    • [三、 二叉树的最大宽度:坐标映射的威力 (Medium)](#三、 二叉树的最大宽度:坐标映射的威力 (Medium))
      • [3.1 题目描述](#3.1 题目描述)
      • [3.2 深度拆解:索引编号法](#3.2 深度拆解:索引编号法)
      • [3.3 C++ 代码实战](#3.3 C++ 代码实战)
    • [四、 在每个树行中找最大值 (Medium)](#四、 在每个树行中找最大值 (Medium))
      • [4.1 题目描述](#4.1 题目描述)
      • [4.2 算法思路](#4.2 算法思路)
      • [4.3 C++ 代码实战](#4.3 C++ 代码实战)
    • [五、 总结:BFS 的涟漪扩散本质](#五、 总结:BFS 的涟漪扩散本质)

BFS 的奥义:涟漪式扩散,掌控全局


一、 N 叉树的层序遍历:打破维度的墙 (Medium)

1.1 题目描述

题目链接429. N 叉树的层序遍历

描述

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

1.2 算法思路:队列控制层级

BFS 的标准模板:

  1. 入队起点:先把根节点扔进队列。
  2. 按层处理 :记录当前队列的大小 sz,这代表了这一层有多少个节点
  3. 循环扩展 :处理完这 sz 个节点,并把它们的所有孩子依次入队。

核心技巧 :通过 q.size() 确定当前层的数量,可以完美区分出结果数组里的每一行。

1.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        vector<vector<int>> ret;
        if (!root) return ret;

        queue<Node*> q;
        q.push(root);

        while (!q.empty()) {
            int sz = q.size(); // 锁定当前层的节点数
            vector<int> currentLevel;

            for (int i = 0; i < sz; i++) {
                Node* t = q.front();
                q.pop();
                currentLevel.push_back(t->val);

                // N 叉树:遍历所有孩子并入队
                for (Node* child : t->children) {
                    if (child) q.push(child);
                }
            }
            ret.push_back(currentLevel);
        }
        return ret;
    }
};

二、 二叉树的锯齿形层序遍历:灵活的翻转 (Medium)

2.1 题目描述

题目链接103. 二叉树的锯齿形层序遍历

描述

先从左往右,再从右往左,层与层之间交替进行。

2.2 算法思路:偶数层大反转

这题没必要在入队、出队顺序上纠结得半死。

  • 第一步 :按照正常的层序遍历,把每一层的数据存进 vector<int> tmp
  • 第二步 :维护一个层数变量 level(从 1 开始)。如果是偶数层,直接把 tmp 反转(reverse) 即可。

ASCII 逻辑图

bash 复制代码
L1: [3] -> 正常
L2: [9, 20] -> 偶数层, 反转 -> [20, 9]
L3: [15, 7] -> 奇数层, 正常

2.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int>> ret;
        if (!root) return ret;

        queue<TreeNode*> q;
        q.push(root);
        int level = 1;

        while (!q.empty()) {
            int sz = q.size();
            vector<int> tmp;
            for (int i = 0; i < sz; i++) {
                TreeNode* 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;
    }
};

三、 二叉树的最大宽度:坐标映射的威力 (Medium)

3.1 题目描述

题目链接662. 二叉树最大宽度

描述

每一层的宽度定义为该层最左和最右非空节点之间的长度(包括中间的 null 节点)。

3.2 深度拆解:索引编号法

这道题不能直接数节点,因为 null 也占位置,如果直接计算时间和空间复杂度都会爆炸。

  • 破局点:利用完全二叉树的性质给节点编号。

    • 根节点编号为 i
    • 左孩子编号为 2 * i
    • 右孩子编号为 2 * i + 1
  • 计算宽度 :每一层的宽度 = 最右节点编号 - 最左节点编号 + 1

细节坑点 :二叉树极深时,编号会溢出 ,远超long long
解药 :使用 unsigned int。在 C++ 中,无符号整型溢出后相减,其结果依然是正确的"距离"(环形模运算特性)。(前提是这两个溢出的数值之差不能超过unsigned int表示最大值,也就是不能超过这个环的一圈)

3.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    int widthOfBinaryTree(TreeNode* root) {
        if (!root) return 0;
        
        // 队列存节点及其对应的编号 (unsigned int 防止溢出报错)
        queue<pair<TreeNode*, unsigned int>> q;
        q.push({root, 1});
        unsigned int maxWidth = 0;

        while (!q.empty()) {
            int sz = q.size();
            unsigned int leftIdx = q.front().second; // 拿到本层最左编号
            unsigned int rightIdx = 0;

            for (int i = 0; i < sz; i++) {
                auto [node, idx] = q.front();
                q.pop();
                rightIdx = idx; // 循环结束时,rightIdx 就是最右编号

                if (node->left) q.push({node->left, idx * 2});
                if (node->right) q.push({node->right, idx * 2 + 1});
            }
            maxWidth = max(maxWidth, rightIdx - leftIdx + 1);
        }
        return (int)maxWidth;
    }
};

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

4.1 题目描述

题目链接515. 在每个树行中找最大值

4.2 算法思路

这是 BFS 最简单的变体。在处理每一层的 sz 个节点时,顺便维护一个 maxVal 变量,遍历完一层后存入结果数组。

4.3 C++ 代码实战

cpp 复制代码
class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        vector<int> ret;
        if (!root) return ret;

        queue<TreeNode*> q;
        q.push(root);

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

            for (int i = 0; i < sz; i++) {
                TreeNode* t = q.front();
                q.pop();
                maxVal = max(maxVal, t->val);

                if (t->left) q.push(t->left);
                if (t->right) q.push(t->right);
            }
            ret.push_back(maxVal);
        }
        return ret;
    }
};

五、 总结:BFS 的涟漪扩散本质

💬 复盘

  1. 层级控制的核心

    BFS 的灵魂在于"按层推进 "。通过 q.size() 锁定当前层节点数,我们就能精准地将问题拆分成一层一层的子问题。

  2. 扩散模型

    BFS 本质是一种从起点向外一圈一圈扩散的过程,就像水波(涟漪)一样:

    • 当前层 = 当前波纹
    • 入队孩子 = 下一层波纹的扩展
  3. 状态附加技巧

    当题目需要额外信息时(如编号、方向、距离等),可以在队列中存:

    cpp 复制代码
    pair<节点, 状态>

    👉 例如:

    • 最大宽度:存 (node, index)
    • 最短路径:存 (node, distance)
  4. 结构不变,逻辑可变

    BFS 的框架几乎固定:

    cpp 复制代码
    while (!q.empty()) {
        int sz = q.size();
        // 处理当前层
    }

    👉 真正变化的是:

    • 每层"统计什么"(最大值 / 顺序 / 宽度)
    • 是否对结果做"变换"(如 reverse)
  5. 降维思想

    BFS 的强大之处在于:

    👉 把"复杂结构问题"转化为"逐层处理问题"

    每一层都是一个"线性问题",难度被大幅降低。


🚀 一句话总结

text 复制代码
BFS = 队列 + 分层 + 扩散

🔥 核心直觉

👉 只要题目出现:

  • "逐层"
  • "最短路径"
  • "扩散 / 传播"

第一反应:BFS!

相关推荐
REDcker1 分钟前
C++ std::move实现原理与vector扩容移动语义
开发语言·c++·c
im_AMBER1 分钟前
Leetcode 158 数组中的第K个最大元素 | 查找和最小的 K 对数字
javascript·数据结构·算法·leetcode·
Ruihong5 分钟前
Vue v-if 转 React:VuReact 怎么处理?
vue.js·react.js·面试
脱氧核糖核酸__6 分钟前
LeetCode热题100——48.旋转图像(题解+答案+要点)
c++·算法·leetcode
木井巳8 分钟前
【递归算法】字母大小写全排列
java·算法·leetcode·决策树·深度优先
宵时待雨9 分钟前
优选算法专题2:滑动窗口
数据结构·c++·笔记·算法
Mr_pyx11 分钟前
LeetCode HOT 100 —— 矩阵置零(多种解法详解)
算法·leetcode·矩阵
葫三生11 分钟前
《论三生原理》系列:文化自信、知识范式重构与科技自主创新的思想运动源头?
大数据·人工智能·科技·深度学习·算法·重构·transformer
我叫Ycg14 分钟前
C++ 中关于插入函数insert() 与 emplace() 的区别与使用建议
开发语言·c++
谭欣辰14 分钟前
区间动态规划精解
c++·动态规划