图论专题(三):“可达性”的探索——DFS/BFS 勇闯「钥匙和房间」
哈喽各位,我是前端小L。
欢迎来到我们的图论专题第三篇!我们已经学会了如何处理“显式”给出的图。但更多时候,图是作为一种“关系模型”,隐藏在问题描述的背后。
今天,我们将扮演一个“开锁达人”。我们手握0号房间的钥匙,每打开一扇门,都可能获得通往更多新门的钥匙。我们的目标是:判断我们能否“解锁”所有的房间。这,本质上是一个关于“可达性 (Reachability)”的经典图论问题。
力扣 841. 钥匙和房间
https://leetcode.cn/problems/keys-and-rooms/

题目分析:
-
输入:一个
rooms数组(列表的列表)。rooms[i]是一个列表,存放着你在i号房间能找到的所有钥匙(即你能从i房打开哪些房门)。 -
起点:你只能从
0号房间开始。 -
目标:判断是否可以访问所有房间。
“Aha!”时刻:将问题“翻译”成图论
-
节点 (Vertex):每一个“房间
i” 都是图中的一个节点。 -
边 (Edge):如果在
i房能拿到j房的钥匙(j在rooms[i]列表中),这就代表一条从i到j的有向边 (Directed Edge)i -> j。 -
建图:等等https://www.google.com/search?q=...
rooms数组vector<vector<int>>,rooms[i]存着所有i能“到达”的节点。这不就是我们心心念念的“邻接表”吗! -
结论:这道题不需要我们“建图”,
rooms数组本身就是图的邻接表!
问题被完美转化: 给定一个有向图(由 rooms 描述),从节点 0 开始,我们能否遍历到图中的所有节点?
解决方案:DFS/BFS 的“标准作业”
既然问题是“可达性”,我们只需要从 0 号节点开始,用 DFS 或 BFS 进行一次“地毯式”的遍历,看看我们总共能访问到多少个节点。
我们还是需要那个“灵魂”—— visited 数组,来防止我们在0->1, 1->0 这样的环里“兜圈”。
算法流程:
-
获取房间总数
n。 -
创建一个
visited数组(或哈希集合),大小为n,初始全为false。 -
创建一个
visited_count计数器,初始为0。 -
启动遍历:从
0号房间开始,执行一次 DFS 或 BFS。-
在 DFS/BFS 的过程中,每当你第一次访问一个房间
u时:-
visited[u] = true -
visited_count++
-
-
然后,探索
u的所有邻居(即rooms[u]列表中的所有key)。
-
-
检查结果:遍历结束后,比较
visited_count是否等于n。
代码实现 (O(V+E) 时间, O(V) 空间)
解法一:DFS (递归)
C++
#include <vector>
#include <stack> // 也可以用递归栈using namespace std;class Solution {
private:int visited_count = 0;void dfs(int u, vector<vector<int>>& rooms, vector<bool>& visited) {// 1. 标记已访问visited[u] = true;visited_count++;// 2. 探索邻居for (int v_key : rooms[u]) {if (!visited[v_key]) {dfs(v_key, rooms, visited);}}}public:bool canVisitAllRooms(vector<vector<int>>& rooms) {int n = rooms.size();vector<bool> visited(n, false);// 从 0 号房间开始dfs(0, rooms, visited);// 检查是否访问了所有房间return visited_count == n;}
};
解法二:BFS (队列)
C++
#include <vector>
#include <queue>using namespace std;class Solution {
public:bool canVisitAllRooms(vector<vector<int>>& rooms) {int n = rooms.size();vector<bool> visited(n, false);queue<int> q;// 1. 启动 BFSq.push(0);visited[0] = true; // 入队时标记int visited_count = 1;while (!q.empty()) {int u = q.front();q.pop();// 2. 探索邻居for (int v_key : rooms[u]) {if (!visited[v_key]) {visited[v_key] = true; // 入队时标记q.push(v_key);visited_count++;}}}// 3. 检查结果return visited_count == n;}
};
深度复杂度分析
-
V (Vertices):顶点数,即
n(房间数)。 -
E (Edges):边数,即所有
rooms列表中的“钥匙”总数。 -
时间复杂度 O(V + E):
-
建图:O(1),因为输入
rooms本身就是邻接表。 -
遍历 (DFS/BFS):我们访问每个房间(顶点
V)最多一次(visited数组保证)。访问每个房间i时,我们会遍历rooms[i]列表,即遍历所有从i出发的“边”。 -
整个遍历过程,我们访问了所有“可达”的顶点和所有从它们出发的“边”。最坏情况下,我们访问了所有
V个顶点和所有E条边。 -
总时间 O(V + E)。
-
-
空间复杂度 O(V):
-
visited数组:需要 O(V) 空间。 -
辅助空间:
-
DFS 需要 O(V) 的递归栈空间(最坏情况,如
0->1->...->n-1)。 -
BFS 需要 O(V) 的队列空间(最坏情况,如
0号房有所有钥匙)。
-
-
总空间 O(V)。
-
总结
今天,我们通过“钥匙和房间”这个生动的例子,学会了如何识别“隐式图”。 我们明白了,图的本质是“节点”和“关系”,只要题目中描述了这两种事物,它就可能是一个图论问题。
rooms 数组,就是一张从 i 到 rooms[i] 的有向图。我们的任务,就是从 0 开始,在这张图上“漫游”,看看能走多远。
在下一篇中,我们将把这个“漫游”的能力,应用到最常见的“隐式图”——二维网格上,去解决大名鼎鼎的“岛屿问题”!
下期见!
