【算法】【学习笔记】广度优先搜索
偷懒中,大部分都由AI润色是也。
核心本质:借助 队列 (FIFO) 按照 波浪式 逐层向外扩散。
天然优势:在 无权图(或边权均相同) 中,第一次访问到目标节点时,所经过的路径 必为最短路径。
一、 核心机制与关键避坑
-
树(Tree)与 图(Graph)的区别
- 树:天然无环,父节点到子节点单向延伸,不需要记录访问状态。
- 图 / 网格:存在环路和回头路,必须使用
visited(数组或 Set)进行去重。
-
铁律:入队即标记(防止 TLE / MLE)
- ❌ 错误做法:在节点 出队(Pop) 时才标记
visited。- 后果:会导致同一个节点在出队前被重复压入队列无数次,直接引发内存爆炸(MLE)或超时(TLE)。
- ✅ 正确做法:在节点 入队(Push/Enqueue) 的同时,立刻 标记
visited = true!
- ❌ 错误做法:在节点 出队(Pop) 时才标记
二、 “节点”到底是什么?(状态 BFS)
不要把“节点”局效于单纯的坐标或点编号:
- 基础节点:空间上的位置,如
(x, y)或node_id。 - 状态节点(多元组):当到达同一个位置存在不同条件限制时,必须把节点抽象为
(位置, 状态)。- 颜色交替最短路:
node = (当前点 u, 上一条边的颜色 color)。同一个点以不同颜色到达属于不同状态。 - 公交路线:
node = busIndex(将“公交车”作为节点,而非“车站”,大幅减少图的边数)。 - 钥匙迷宫:
node = (x, y, bitmask_keys)。持有不同钥匙到达同一坐标属于不同状态。
- 颜色交替最短路:
三、 按层遍历通用代码模板(求最短步数)
模板核心逻辑
- 初始化
queue,将起点入队,同时标记visited。 - 开启
while (!queue.empty())循环。 - 关键:记录当前层的节点数
size = queue.size(),一次性处理完整层节点。 - 每处理完一层,步数
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 | 图的边权仅有 0 和 1 两类(如平移消耗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,最后找到到站即可。
但很快就可以看出这个思路漏洞百出,完全没有闭环的条件。
误区介绍:
-
每次只能从 route 的首或尾坐
实际上:公交车是在整条线路上循环开的。你可以在路线上的任意一个车站上车,并在该线路的任意一个车站下车。
等价转化:只要你上了第 \(i\) 辆公交车,你就能在 1 次乘车内直接到达 routes[i] 里的所有车站。 -
从 routes[0] 开始坐
读题不认真!是从起点 source 开始坐!
所以需要找出所有经过 source 站的公交线路,将这些线路全部作为第一批起点压入队列(多源 BFS)。 -
站到站的广度搜索
会导致内存爆炸!!如果把“车站”当作图的节点建立“站 \(\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 辅助整理。

浙公网安备 33010602011771号