AIGC标识 【算法】【学习笔记】广度优先搜索

偷懒中,大部分都由AI润色是也。

核心本质:借助 队列 (FIFO) 按照 波浪式 逐层向外扩散。
天然优势:在 无权图(或边权均相同) 中,第一次访问到目标节点时,所经过的路径 必为最短路径


一、 核心机制与关键避坑

  1. 树(Tree)与 图(Graph)的区别

    • :天然无环,父节点到子节点单向延伸,不需要记录访问状态。
    • 图 / 网格:存在环路和回头路,必须使用 visited(数组或 Set)进行去重。
  2. 铁律:入队即标记(防止 TLE / MLE)

    • 错误做法:在节点 出队(Pop) 时才标记 visited
      • 后果:会导致同一个节点在出队前被重复压入队列无数次,直接引发内存爆炸(MLE)或超时(TLE)。
    • 正确做法:在节点 入队(Push/Enqueue) 的同时,立刻 标记 visited = true

二、 “节点”到底是什么?(状态 BFS)

不要把“节点”局效于单纯的坐标或点编号:

  • 基础节点:空间上的位置,如 (x, y)node_id
  • 状态节点(多元组):当到达同一个位置存在不同条件限制时,必须把节点抽象为 (位置, 状态)
    • 颜色交替最短路node = (当前点 u, 上一条边的颜色 color)。同一个点以不同颜色到达属于不同状态。
    • 公交路线node = busIndex(将“公交车”作为节点,而非“车站”,大幅减少图的边数)。
    • 钥匙迷宫node = (x, y, bitmask_keys)。持有不同钥匙到达同一坐标属于不同状态。

三、 按层遍历通用代码模板(求最短步数)

模板核心逻辑

  1. 初始化 queue,将起点入队,同时标记 visited
  2. 开启 while (!queue.empty()) 循环。
  3. 关键:记录当前层的节点数 size = queue.size(),一次性处理完整层节点。
  4. 每处理完一层,步数 step++

BFS模板

int bfs(Node start, Node target) {
    queue<Node> q;
    unordered_set<Node> visited; // 或 vector<bool> / vector<vector<bool>>

    // 1. 起点入队并标记
    q.push(start);
    visited.insert(start);

    int step = 0; // 记录步数/距离

    // 2. 队列不为空时继续扩散
    while (!q.empty()) {
        int size = q.size(); // 锁定当前层的节点数量

        while (size--) {
            Node cur = q.front();
            q.pop();

            // 到达终点,直接返回当前步数(必定是最短路径)
            if (cur == target) return step;

            // 扩展相邻节点
            for (Node next : getNeighbors(cur)) {
                if (!visited.count(next)) {
                    visited.insert(next); // 必须在入队时立刻标记!
                    q.push(next);
                }
            }
        }
        step++; // 整层处理完毕,步数 +1
    }

    return -1; // 无法到达 target
}

四、 BFS 四大经典形态

形态 核心场景 关键处理技巧
单源 BFS 单起点扩散(如常规迷宫、单点最短路) 标准模板处理。
多源 BFS 多个起点同时向外扩散(如“腐烂的橘子”、“多陆地侵蚀”) 在第 0 步将所有起点一次性全部压入队列,逻辑上相当于建立了一个“虚拟超级源点”。
双向 BFS 已知起点和终点,且状态空间极庞大(如单词接龙、八数码) 从起点和终点同时向中间扩散,优先拓展节点较少的那一端队列。复杂度从 \(O(b^d)\) 降至 \(O(b^{d/2})\)
0-1 BFS 图的边权仅有 01 两类(如平移消耗0,转向消耗1) 使用 双端队列 (Deque):权值为 0 的边加到 队头,权值为 1 的边加到 队尾。时间复杂度 \(O(V + E)\)

❔题目

1. 地图中的最高点

解题思路

这个题目,有点像,可以应用于游戏中的地形自动生成模式(MC那种),但是完全没思路怎么做!

这时候需要转换思维:

  • 水域高度固定为 0。
  • 要让陆地高度最大,每个陆地的高度实际上就是它距离最近水域的曼哈顿距离。

使用多源 BFS :

  • 初始化时,把所有水域的坐标同时压入队列,高度记为 0。
  • BFS 每次向上下左右扩散一步,新扩到的陆地高度 = 当前高度 + 1。
  • 水域像“波浪”一样同时向外扩散,最先到达陆地的波浪,给出的高度就是该陆地能达到的最大合法高度。

实现

function highestPeak(isWater: number[][]): number[][] {
    const m = isWater.length;
    const n = isWater[0].length;
    const height: number[][] = Array.from({ length: m }, () => Array(n).fill(-1));
    const queue: [number, number][] = [];

    // 1. 将所有水域作为起点加入队列,高度设为 0
    for (let r = 0; r < m; r++) {
        for (let c = 0; c < n; c++) {
            if (isWater[r][c] === 1) {
                height[r][c] = 0;
                queue.push([r, c]);
            }
        }
    }

    // 四个方向
    const dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];
    let head = 0; // 模拟队列,避免 shift() 的 O(N) 性能开销

    // 2. 多源 BFS:像水波一样一层层向外扩展
    while (head < queue.length) {
        const [r, c] = queue[head++];

        for (const [dr, dc] of dirs) {
            const nr = r + dr;
            const nc = c + dc;

            // 如果没越界且未被访问过(height === -1)
            if (nr >= 0 && nr < m && nc >= 0 && nc < n && height[nr][nc] === -1) {
                height[nr][nc] = height[r][c] + 1;
                queue.push([nr, nc]);
            }
        }
    }

    return height;
}
public class Solution {
    public int[][] HighestPeak(int[][] isWater) {
        // 获得地图大小
        int m = isWater.Length, n = isWater[0].Length;
        // 创建高度数组
        int[][] height = Enumerable.Range(0, m)
            .Select(_ => Enumerable.Repeat(-1, n).ToArray()).ToArray();

        // 初始化水体高度,并且存入队列,用于BFS
        Queue<(int x, int y)> queue = new Queue<(int, int)>();
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(isWater[i][j] == 1){
                    height[i][j] = 0;
                    queue.Enqueue((i, j));
                }
            }
        }

        // 方向
        var dir = new (int x, int y)[] {(1, 0), (-1, 0), (0, 1), (0,-1)};
        // 进行BFS,当队列不为空时,进行搜索
        while(queue.Count > 0){
            var (x, y) = queue.Dequeue();
            foreach(var d in dir){
                var nx = x + d.x;
                var ny = y + d.y;
                // 没超界并且没有搜索过的,根据基准点+1
                if(nx >= 0 && nx < m && ny >= 0 && ny < n && height[nx][ny] == -1){
                    height[nx][ny] = height[x][y] + 1;
                    queue.Enqueue((nx, ny));
                }
            }
        }
        return height;
    }
}
class Solution {
public:
    vector<vector<int>> highestPeak(vector<vector<int>>& isWater) {
        int m = isWater.size();
        int n = isWater[0].size();
        vector<vector<int>> height(m, vector<int>(n, -1));
        queue<pair<int, int>> q;

        // 1. 所有水域入队
        for (int r = 0; r < m; ++r) {
            for (int c = 0; c < n; ++c) {
                if (isWater[r][c] == 1) {
                    height[r][c] = 0;
                    q.push({r, c});
                }
            }
        }

        int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};

        // 2. 多源 BFS
        while (!q.empty()) {
            auto [r, c] = q.front();
            q.pop();

            for (auto& d : dirs) {
                int nr = r + d[0];
                int nc = c + d[1];

                if (nr >= 0 && nr < m && nc >= 0 && nc < n && height[nr][nc] == -1) {
                    height[nr][nc] = height[r][c] + 1;
                    q.push({nr, nc});
                }
            }
        }

        return height;
    }
};

复杂度分析

  • 时间复杂度\(O(M \times N)\)
    网格中每个格子最多入队、出队一次。
  • 空间复杂度\(O(M \times N)\)
    用于存储结果数组 height 和 BFS 队列。

2. 获取你好友已观看的视频

解题思路

由题可以看出,实质为:level代表要广度优先搜索到哪一层,先从id获得第一个friend,然后进行记录视频并且搜索。

不过要注意:题目要求最短距离为当前level。为了避免一个人既是你的 1 级好友,又通过其他人被重复算作 2 级好友,必须从起点 id 开始配合 visited 标记数组。

具体步骤:

  • 分层 BFS 扩散:从 id 出发入队并标记 visited[id] = true,控制循环按层扩散,正好扩展 level 次。
  • 收集目标层视频:此时队列里剩余的所有人,恰好都是最短距离等于 \(level\) 的好友。取出这些人,用哈希表(Map/Dictionary)统计他们看过的所有视频及频次。
  • 多条件排序:将统计好的视频按照观看频次升序排列;若频次相同,按字符串字典序升序排列。

实现

function watchedVideosByFriends(watchedVideos: string[][], friends: number[][], id: number, level: number): string[] {
    const n = friends.length;
    const visited = new Array(n).fill(false);
    const queue: number[] = [id];
    visited[id] = true;

    let currentLevel = 0;
    // 用head避免shift,head >= queue.length时说明队列为空
    let head = 0;

    // 1. 按层 BFS 扩散,只走 level 步,将朋友都塞入queue中
    while (head < queue.length && currentLevel < level) {
        const size = queue.length - head;
        for (let i = 0; i < size; i++) {
            const u = queue[head++];
            for (const v of friends[u]) {
                if (!visited[v]) {
                    visited[v] = true;
                    queue.push(v);
                }
            }
        }
        currentLevel++;
    }

    // 2. 统计队列中好友(即第 level 层好友)的视频频次
    const freqMap = new Map<string, number>();
    while (head < queue.length) {
        const u = queue[head++];
        for (const video of watchedVideos[u]) {
            freqMap.set(video, (freqMap.get(video) || 0) + 1);
        }
    }

    // 3. 排序:频次升序 -> 字典序升序
    return Array.from(freqMap.keys()).sort((a, b) => {
        const freqA = freqMap.get(a)!;
        const freqB = freqMap.get(b)!;
        if (freqA !== freqB) return freqA - freqB;
        return a.localeCompare(b);
    });
}
public class Solution {
    public IList<string> WatchedVideosByFriends(IList<IList<string>> watchedVideos, int[][] friends, int id, int level) {
        int n = friends.Length;
        bool[] visited = new bool[n];
        var queue = new Queue<int>();

        queue.Enqueue(id);
        visited[id] = true;

        int currentLevel = 0;
        // 1. 按层 BFS 扩散
        while (queue.Count > 0 && currentLevel < level) {
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                int u = queue.Dequeue();
                foreach (int v in friends[u]) {
                    if (!visited[v]) {
                        visited[v] = true;
                        queue.Enqueue(v);
                    }
                }
            }
            currentLevel++;
        }

        // 2. 统计第 level 层好友的视频频次
        var freqMap = new Dictionary<string, int>();
        while (queue.Count > 0) {
            int u = queue.Dequeue();
            foreach (var video in watchedVideos[u]) {
                if (!freqMap.ContainsKey(video)) freqMap[video] = 0;
                freqMap[video]++;
            }
        }

        // 3. 自定义双条件排序
        var result = freqMap.Keys.ToList();
        result.Sort((a, b) => {
            if (freqMap[a] != freqMap[b]) return freqMap[a].CompareTo(freqMap[b]);
            return string.CompareOrdinal(a, b);
        });

        return result;
    }
}
class Solution {
public:
    vector<string> watchedVideosByFriends(vector<vector<string>>& watchedVideos, vector<vector<int>>& friends, int id, int level) {
        int n = friends.size();
        vector<bool> visited(n, false);
        queue<int> q;

        q.push(id);
        visited[id] = true;

        int currentLevel = 0;
        // 1. 按层 BFS 扩散
        while (!q.empty() && currentLevel < level) {
            int size = q.size();
            for (int i = 0; i < size; ++i) {
                int u = q.front();
                q.pop();
                for (int v : friends[u]) {
                    if (!visited[v]) {
                        visited[v] = true;
                        q.push(v);
                    }
                }
            }
            currentLevel++;
        }

        // 2. 统计第 level 层好友的视频频次
        unordered_map<string, int> freqMap;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (const string& video : watchedVideos[u]) {
                freqMap[video]++;
            }
        }

        // 3. 提取并排序
        vector<pair<string, int>> vec(freqMap.begin(), freqMap.end());
        sort(vec.begin(), vec.end(), [](const pair<string, int>& a, const pair<string, int>& b) {
            if (a.second != b.second) return a.second < b.second;
            return a.first < b.first;
        });

        vector<string> res;
        for (const auto& p : vec) res.push_back(p.first);
        return res;
    }
};

复杂度分析

  • 时间复杂度\(O(N + E + V \log V)\)
    其中 \(N\) 为总人数,\(E\) 为好友关系网络中的边数,\(V\) 为第 \(level\) 层好友观看到的视频不同种类数。
    BFS 过程遍历每个节点与边最多一次,后续对视频种类进行自定义排序需要消耗 \(O(V \log V)\)
  • 空间复杂度\(O(N + V)\)
    需要 \(O(N)\) 用于 visited 数组和 BFS 队列,需要 \(O(V)\) 用于储存视频词频哈希表。

3. 颜色交替的最短路径

解题思路

原始思路:
要交替出现,所以需要交替进行搜索,每一层代表的是比如0的时候,就是0点到0点的最短路径长度,然后1就是0到1,2就是0到2,

中间的最短能保证是后面的最短路径吗?不可以,所以先创建好长度为n的数组,并且默认值为-1,然后遇到当前点时,将当前距离立刻塞入到数组中,当距离不是-1时,不更新(因为必定长于已经塞入的距离)。

计划使用全局len与2的余数来判断是否用红边还是蓝边,但是这踩了两个个坑:

  • 开头不一定是红边,同一个节点 \(u\) 被“红边”到达和被“蓝边”到达,本质上是两个完全不同的状态。
  • 不同路径推进到同一个节点的颜色要求是不一致的。

核心逻辑修正

队列存双元组 (node, color):

  • color = 0 表示上一条是红边(下一步必须走蓝);
  • color = 1 表示上一条是蓝边(下一步必须走红)。

起点双入队:从 \(0\) 出发,既可以第一步走红边,也可以第一步走蓝边,所以初始化把 (0, 0) 和 (0, 1) 都推进队列。

二维 visited[n][2] 标记:允许同一个节点分别以“红边到达”和“蓝边到达”各进队一次,但同一种颜色到达后不再重复进队,防止死循环。

实现

function shortestAlternatingPaths(n: number, redEdges: number[][], blueEdges: number[][]): number[] {
    const redMap: number[][] = Array.from({ length: n }, () => []);
    const blueMap: number[][] = Array.from({ length: n }, () => []);

    for (const [u, v] of redEdges) redMap[u].push(v);
    for (const [u, v] of blueEdges) blueMap[u].push(v);

    // visited[u][color]: color 0 = 红, 1 = 蓝
    const visited: boolean[][] = Array.from({ length: n }, () => [false, false]);
    const queue: [number, number][] = [[0, 0], [0, 1]];
    visited[0][0] = true;
    visited[0][1] = true;

    const res = new Array(n).fill(-1);
    let step = 0;
    let head = 0;

    while (head < queue.length) {
        const size = queue.length - head;
        for (let i = 0; i < size; i++) {
            const [u, color] = queue[head++];

            if (res[u] === -1) {
                res[u] = step;
            }

            if (color === 0) {
                for (const v of blueMap[u]) {
                    if (!visited[v][1]) {
                        visited[v][1] = true;
                        queue.push([v, 1]);
                    }
                }
            } else {
                for (const v of redMap[u]) {
                    if (!visited[v][0]) {
                        visited[v][0] = true;
                        queue.push([v, 0]);
                    }
                }
            }
        }
        step++;
    }

    return res;
}
public class Solution {
    public int[] ShortestAlternatingPaths(int n, int[][] redEdges, int[][] blueEdges) {
        var redMap = new List<int>[n];
        var blueMap = new List<int>[n];
        for (int i = 0; i < n; i++) {
            redMap[i] = new List<int>();
            blueMap[i] = new List<int>();
        }

        foreach (var e in redEdges) redMap[e[0]].Add(e[1]);
        foreach (var e in blueEdges) blueMap[e[0]].Add(e[1]);

        // visited[u, color]: color 0 = 红, 1 = 蓝
        bool[,] visited = new bool[n, 2];
        var queue = new Queue<(int node, int color)>();

        queue.Enqueue((0, 0));
        queue.Enqueue((0, 1));
        visited[0, 0] = true;
        visited[0, 1] = true;

        int[] res = new int[n];
        Array.Fill(res, -1);
        int step = 0;

        while (queue.Count > 0) {
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                var (u, color) = queue.Dequeue();

                if (res[u] == -1) {
                    res[u] = step;
                }

                if (color == 0) {
                    foreach (int v in blueMap[u]) {
                        if (!visited[v, 1]) {
                            visited[v, 1] = true;
                            queue.Enqueue((v, 1));
                        }
                    }
                } else {
                    foreach (int v in redMap[u]) {
                        if (!visited[v, 0]) {
                            visited[v, 0] = true;
                            queue.Enqueue((v, 0));
                        }
                    }
                }
            }
            step++;
        }

        return res;
    }
}
class Solution {
public:
    vector<int> shortestAlternatingPaths(int n, vector<vector<int>>& redEdges, vector<vector<int>>& blueEdges) {
        vector<vector<int>> redMap(n), blueMap(n);
        for (auto& e : redEdges) redMap[e[0]].push_back(e[1]);
        for (auto& e : blueEdges) blueMap[e[0]].push_back(e[1]);

        // visited[u][color]: color 0 = 红, 1 = 蓝
        vector<vector<bool>> visited(n, vector<bool>(2, false));

        // 队列存 {当前节点, 上一条边的颜色}
        queue<pair<int, int>> q;

        // 起点初始化:分别试着走蓝或红
        q.push({0, 0});
        q.push({0, 1});
        visited[0][0] = true;
        visited[0][1] = true;

        vector<int> res(n, -1);
        int step = 0;

        while (!q.empty()) {
            int size = q.size();
            while (size--) {
                auto [u, color] = q.front();
                q.pop();

                // 第一次访问到节点 u 时记录的最短步数就是最终答案
                if (res[u] == -1) {
                    res[u] = step;
                }

                // 如果上一条是红(0),接下来必须走蓝(1)
                if (color == 0) {
                    for (int v : blueMap[u]) {
                        if (!visited[v][1]) {
                            visited[v][1] = true;
                            q.push({v, 1});
                        }
                    }
                } 
                // 如果上一条是蓝(1),接下来必须走红(0)
                else {
                    for (int v : redMap[u]) {
                        if (!visited[v][0]) {
                            visited[v][0] = true;
                            q.push({v, 0});
                        }
                    }
                }
            }
            step++; // 每一层结束后步数 +1
        }

        return res;
    }
};

复杂度分析

  • 时间复杂度\(O(V + E)\)
    每个节点最多以 2 种状态入队,每条边最多被遍历 2 次。
  • 空间复杂度\(O(V + E)\)
    建图邻接表与队列占用的空间。

4. 公交路线

解题思路

初始思路:
每次只能从route的首或者尾坐,所以先遍历routes,把每个站的邻接表建立起来。

然后从routes[0]开始坐,先搜索搜索当前route的站,假如到了target,说明到站,假如没有,则对每个站进行广度搜索,深入一层就+1step,最后找到到站即可。


但很快就可以看出这个思路漏洞百出,完全没有闭环的条件。

误区介绍:

  1. 每次只能从 route 的首或尾坐

    实际上:公交车是在整条线路上循环开的。你可以在路线上的任意一个车站上车,并在该线路的任意一个车站下车
    等价转化:只要你上了第 \(i\) 辆公交车,你就能在 1 次乘车内直接到达 routes[i] 里的所有车站。

  2. 从 routes[0] 开始坐

    读题不认真!是从起点 source 开始坐!
    所以需要找出所有经过 source 站的公交线路,将这些线路全部作为第一批起点压入队列(多源 BFS)。

  3. 站到站的广度搜索

    会导致内存爆炸!!如果把“车站”当作图的节点建立“站 \(\rightarrow\) 站”邻接表,一条包含 1000 个站的路线就会产生 \(1000 \times 1000\) 条边,建图开销巨大。
    正确建模(点换成“车”):

    • 建立车站 -> 经过该站的公交线路列表的映射。队列里存的是公交线路,而不是车站
    • 上了一辆车 \(\rightarrow\) 步数 step + 1;
    • 遍历这辆车上的所有车站,如果遇到了 target,直接返回 step;如果没遇到,就把这些车站能换乘的其他“未乘坐过的公交车”继续压入队列。

实现

function numBusesToDestination(routes: number[][], source: number, target: number): number {
    // 特判:如果起点和终点相同,不需要坐车
    if (source === target) return 0;

    // 1. 建立 车站 -> 经过该车站的公交线路列表 的映射
    const stationToBuses = new Map<number, number[]>();
    for (let i = 0; i < routes.length; i++) {
        for (const station of routes[i]) {
            if (!stationToBuses.has(station)) {
                stationToBuses.set(station, []);
            }
            stationToBuses.get(station)!.push(i);
        }
    }

    // 如果起点或终点根本没有任何公交车经过,无法到达
    if (!stationToBuses.has(source) || !stationToBuses.has(target)) return -1;

    // 2. BFS 队列(存的是公交线路的索引 busIndex)
    const queue: number[] = [];
    const visitedBuses = new Array(routes.length).fill(false);
    const visitedStations = new Set<number>();

    // 将包含 source 车站的所有公交线路作为第一批起点入队
    for (const bus of stationToBuses.get(source)!) {
        visitedBuses[bus] = true;
        queue.push(bus);
    }
    visitedStations.add(source);

    let step = 1; // 登上第一批公交车,坐车次数为 1
    let head = 0; // 模拟队列指针,提升性能

    while (head < queue.length) {
        const size = queue.length - head;
        for (let i = 0; i < size; i++) {
            const bus = queue[head++];

            // 检查这趟公交车能到达的所有车站
            for (const station of routes[bus]) {
                if (station === target) return step;

                // 如果这个车站还没访问过,通过它寻找可换乘的新公交车
                if (!visitedStations.has(station)) {
                    visitedStations.add(station);
                    const nextBuses = stationToBuses.get(station) || [];
                    for (const nextBus of nextBuses) {
                        if (!visitedBuses[nextBus]) {
                            visitedBuses[nextBus] = true;
                            queue.push(nextBus);
                        }
                    }
                }
            }
        }
        step++; // 换乘下一趟车,步数 +1
    }

    return -1;
}
public class Solution {
    public int NumBusesToDestination(int[][] routes, int source, int target) {
        if (source == target) return 0;

        // 1. 建立 车站 -> 线路 的映射
        var stationToBuses = new Dictionary<int, List<int>>();
        for (int i = 0; i < routes.Length; i++) {
            foreach (int station in routes[i]) {
                if (!stationToBuses.ContainsKey(station)) {
                    stationToBuses[station] = new List<int>();
                }
                stationToBuses[station].Add(i);
            }
        }

        if (!stationToBuses.ContainsKey(source) || !stationToBuses.ContainsKey(target)) return -1;

        // 2. BFS 队列(存 busIndex)
        var queue = new Queue<int>();
        bool[] visitedBuses = new bool[routes.Length];
        var visitedStations = new HashSet<int>();

        foreach (int bus in stationToBuses[source]) {
            visitedBuses[bus] = true;
            queue.Enqueue(bus);
        }
        visitedStations.Add(source);

        int step = 1;

        while (queue.Count > 0) {
            int size = queue.Count;
            for (int i = 0; i < size; i++) {
                int bus = queue.Dequeue();

                foreach (int station in routes[bus]) {
                    if (station == target) return step;

                    if (!visitedStations.Contains(station)) {
                        visitedStations.Add(station);
                        if (stationToBuses.TryGetValue(station, out var nextBuses)) {
                            foreach (int nextBus in nextBuses) {
                                if (!visitedBuses[nextBus]) {
                                    visitedBuses[nextBus] = true;
                                    queue.Enqueue(nextBus);
                                }
                            }
                        }
                    }
                }
            }
            step++;
        }

        return -1;
    }
}
class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {
        if (source == target) return 0;

        // 1. 建立 车站 -> 线路 的映射
        unordered_map<int, vector<int>> stationToBuses;
        for (int i = 0; i < routes.size(); ++i) {
            for (int station : routes[i]) {
                stationToBuses[station].push_back(i);
            }
        }

        if (!stationToBuses.count(source) || !stationToBuses.count(target)) return -1;

        // 2. BFS 队列(存 busIndex)
        queue<int> q;
        vector<bool> visitedBuses(routes.size(), false);
        unordered_set<int> visitedStations;

        for (int bus : stationToBuses[source]) {
            visitedBuses[bus] = true;
            q.push(bus);
        }
        visitedStations.insert(source);

        int step = 1;

        while (!q.empty()) {
            int size = q.size();
            while (size--) {
                int bus = q.front();
                q.pop();

                for (int station : routes[bus]) {
                    if (station == target) return step;

                    if (!visitedStations.count(station)) {
                        visitedStations.insert(station);
                        for (int nextBus : stationToBuses[station]) {
                            if (!visitedBuses[nextBus]) {
                                visitedBuses[nextBus] = true;
                                q.push(nextBus);
                            }
                        }
                    }
                }
            }
            step++;
        }

        return -1;
    }
};

复杂度分析

  • 时间复杂度\(O(\sum \vert{}routes[i]\vert{})\)
    即所有公交线路车站数量的总和。图中每趟公交车和每个车站最多进出队列一次。
  • 空间复杂度\(O(\sum \vert{}routes[i]\vert{})\)
    用于存储哈希映射、队列及访问标记。

🤯拓展

DFS BFS
核心思想 一路深入 逐层扩散
常用结构 递归 / 栈 队列
特点 先走深 先走近
图遍历 通常需要 visited 通常需要 visited
最短路径 不保证 无权图保证
常见扩展 回溯 多源 / 分层

引用

[1] 力扣探索模式


注:本文为个人学习与刷题笔记,部分文本结构与排版格式由 AI 辅助整理。

posted @ 2026-08-24 23:56  SEHOD  阅读(5)  评论(0)    收藏  举报