题目描述
每个房间里放着若干钥匙,每把钥匙通往一个房间。从 0 号房开始,问能否访问到所有房间?
思路解析
最朴素的想法:维护一堆已拿到的钥匙,每轮扫一遍看能开哪些还没开的门。可你会一次次重新检查那些早就开过的房间,钥匙一多就大量重复。
转折:把它看成图——房间是节点,钥匙是有向边。从 0 开始 BFS/DFS,每进一个房就立刻标记 visited,只对没访问过的房入队。这样每个房只处理一次,不再回头重扫。最后数 visited 等于房间总数就是 true。
建图 · 0 入队:画成图:rooms[0]=[1]、rooms[1]=[2]、rooms[2]=[3]、rooms[3]=[],即三条有向边 0→1→2→3。起点 0 标记 visited 并入队,作为遍历的源头。
出队 0 · 看它的钥匙:取出 0(标橙=正在处理),它已访问。看它手上的钥匙 rooms[0]=[1],准备去开 1 号房。
0→1 · 1 没访问过 → 入队:沿边 0→1:房 1 还没访问,所以拿钥匙开门、标记 visited、入队。0 处理完变绿(done)。
出队 1 · 看它的钥匙:取出 1 处理。它的钥匙 rooms[1]=[2],指向房 2。
1→2 · 2 没访问过 → 入队:沿边 1→2:房 2 没访问过,开门、标记、入队。注意每次进房第一件事就是标 visited——这是防止绕回来死循环的关键。
出队 2 · 1→2 已访问(负例:跳过):取出 2 处理,它的钥匙 rooms[2]=[3]。设想此刻又冒出一把指回 1 的钥匙:房 1 已经在 visited 里——直接跳过、不再入队。这就是负例:已访问的房一律不重复处理。
2→3 · 3 没访问过 → 入队:沿边 2→3:房 3 没访问,开门、标记、入队。
出队 3 · 没钥匙了 · 队列空:取出 3 处理,rooms[3]=[] 没钥匙了。没有新房可入队,队列空,遍历结束。
判断 · 数 visited:数一下 visited:4 个,正好等于房间总数 4 → 全部可达,返回 true。若某房没人给钥匙、永远进不了 visited,最终 len(visited) 小于 N,就返回 false。
凡是「从一个点出发能不能走到所有点」「有几个连通块」,都用 BFS/DFS 数可达节点。钥匙房间、被围绕的区域、省份数量,全是这一类。
参考代码(Python)
1visited = {0} # 起点先标记
2q = deque([0])
3while q:
4 room = q.popleft()
5 for key in rooms[room]: # 这房里的每把钥匙
6 if key not in visited: # 只开没访问过的门
7 visited.add(key) # 进门立刻标记
8 q.append(key)
9return len(visited) == len(rooms) # 全访问到才 true复杂度分析
- 时间复杂度:O(N+E) —— N 个房各出队一次,E 把钥匙各检查一次
- 空间复杂度:O(N) —— visited 集合 + 队列最多装 N 个房
套路模板
记住骨架:起点先标记、邻居只处理没访问的、入队前立刻加 visited、最后数 visited。把队列换成递归就是 DFS,效果一样。
1# 「从起点能到达哪些点」都套
2visited = {start}; q = deque([start])
3while q:
4 u = q.popleft()
5 for v in graph[u]:
6 if v not in visited: # 只走没访问的
7 visited.add(v); q.append(v)
8# len(visited) == N ? 全连通易错点
- 错误写法:入队 / 进 dfs 才忘标 visited → 正确写法:加入 visited 和入队同时做(不标记遇到指回来的钥匙会重复入队,有环时无限循环)
- 错误写法:判断只看「有没有到某个终点」 → 正确写法:比对 len(visited) == len(rooms)(题目要的是访问到全部房间,不是到达某一个)
以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握。