当前:LC841 · LC841 钥匙和房间:从 0 号房间出发,能否进入所有房间 · 首次出现于 Day 32 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC841图算法图 DFS/BFS · 有向图可达性图搜索前线

LC841 钥匙和房间:从 0 号房间出发,能否进入所有房间

你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?

题目是什么

你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?

解决什么问题

当前节点的合法邻居是谁,访问标记何时写入?

核心结论

把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。

01交互算法精讲

先说结论:这道题到底解决什么

怎样从“你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?”推导出 图 DFS/BFS · 有向图可达性,并证明每次状态变化都不会漏掉答案?

中心结论:把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。

读完必须能回答
  1. 1.暴力方案在哪里重复计算,为什么仍然是正确基线?
  2. 2.当前节点的合法邻居是谁,访问标记何时写入?
  3. 3.不变量“从 0 号节点出发的有向图可达性问题,不要求回到 0。”为什么能保证算法安全前进?
02交互算法精讲

完整题目与题意拆解

有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。

在形式上,对于每个房间 i 都有一个钥匙列表 rooms[i],每个钥匙 rooms[i][j] 由 [0,1,...,N-1] 中的一个整数表示,其中 N = rooms.length。 钥匙 rooms[i][j] = v 可以打开编号为 v 的房间。最初,除 0 号房间外的其余所有房间都被锁住。你可以自由地在房间之间来回走动。如果能进入每个房间返回 true,否则返回 false。

1 <= rooms.length <= 1000 0 <= rooms[i].length <= 1000 所有房间中的钥匙数量总计不超过 3000。

在本站主例中,你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?

算法最终需要得到或观察:遍历结束后 visited 全 true 返回 true,否则 false。

把题目翻译成状态
  • 输入:你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?
  • 机器需要维护:stack/queue 存待处理房间,visited 数组记录哪些房间已打开。
  • 最终可观察结果:遍历结束后 visited 全 true 返回 true,否则 false。
动画 1 · 题意扫描

先看清算法到底要维护什么

先建立输入、目标、输出和第一批状态,不急着进入模板。

Step 1/20%
题目与输入建立输入、目标与算法心智

rooms=[[1,3],[3,0,1],[2],[0]],0 号房间默认打开

正在加载算法场景...
03交互算法精讲

第一层方案:暴力做法

重复从多个位置搜索会反复访问同一状态;统一的 visited 与前线结构让每个状态只处理一次。

暴力方案的价值是确认题意并提供正确性基线。它通常会覆盖所有候选, 但没有保存已经确认的信息,因此同一状态会被重新计算。

动画 2 · 暴力重复

重复工作究竟发生在哪里

把重复读取或重复搜索的区域明确标出,再决定优化必须保存什么。

Step 1/20%
先做对:建立暴力基线枚举所有候选并完整验证

rooms=[[1,3],[3,0,1],[2],[0]],0 号房间默认打开

正在加载算法场景...
优化方向:把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。
04交互算法精讲

整体地图:先做什么,再做什么

  1. 1建模把输入翻译成“图搜索前线”,明确答案需要观察什么。
  2. 2状态只维护 stack/queue 存待处理房间,visited 数组记录哪些房间已打开。
  3. 3转移每一步按照 把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。
  4. 4收尾读取 遍历结束后 visited 全 true 返回 true,否则 false。,并复核边界与复杂度。
05交互算法精讲

图搜索前线:核心概念

先把题目对象翻译成节点和边,再选择 BFS 或 DFS。

这道题不是为了记住一个组件名称,而是为了让状态具有可解释的语义:stack/queue 存待处理房间,visited 数组记录哪些房间已打开。

核心不变量
  • 从 0 号节点出发的有向图可达性问题,不要求回到 0。
  • visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
动画 3 · 核心概念

建立“图搜索前线”心智模型

用主例建立核心状态,先预测下一步,再公开正确分支和理由。

Step 1/30%
题目与输入建立输入、目标与算法心智

rooms=[[1,3],[3,0,1],[2],[0]],0 号房间默认打开

正在加载算法场景...
06交互算法精讲

核心机制:状态如何一步步变化

给出一个房间数组,每个房间里面装了一些钥匙。0 号房间默认是可以进入的,房间进入顺序没有要求,问最终能否进入所有房间。 用 DFS 依次深搜所有房间的钥匙,如果都能访问到,最终输出 true。这题算是 DFS 里面的简单题。

把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。

执行过程中持续维护:stack/queue 存待处理房间,visited 数组记录哪些房间已打开。

正确性依赖以下不变量:从 0 号节点出发的有向图可达性问题,不要求回到 0。;visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。

面试时可以压缩为:建模成有向图可达性:房间是节点、钥匙是有向边,从 0 做 DFS/BFS,用 visited 避免重复访问和环,最后看 visited 是否全 true。时间 O(n+m),空间 O(n)。

落到当前题,执行机制可以压缩为:把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。每一次更新都必须保持核心不变量,而不是只让样例碰巧得到正确答案。

动画 4 · 机制构建

一次状态转移为什么成立

集中观察一次选择、计算和状态写回,让变量变化与原因同时出现。

Step 1/50%
访问 Room 0,钥匙 [1,3]for key in rooms[0]: if !visited[key] push

Room 0 打开了 R1、R3

正在加载算法场景...
07交互算法精讲

正确性证明:为什么不会漏答案

初始化

初始化:算法开始时,全部合法候选仍在状态表示范围内;从 0 号节点出发的有向图可达性问题,不要求回到 0。

保持

保持:执行“把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。”时,只删除已经能证明不可能的候选,并把新信息写回 stack/queue 存待处理房间,visited 数组记录哪些房间已打开。

终止

终止:没有待处理状态或达到命中条件时,当前可观察结果就是“遍历结束后 visited 全 true 返回 true,否则 false。”。

正确性抓手不是“样例跑通”,而是每一帧结束后仍能复述:从 0 号节点出发的有向图可达性问题,不要求回到 0。;visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
08交互算法精讲

完整执行过程

  1. 1题目与输入你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间? 因为:把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。
  2. 2从 0 号房间出发做 DFS0 号房间一开始就是打开的,入栈。 因为:钥匙是有向边,从 0 出发遍历所有可达房间。
  3. 3访问 Room 0,钥匙 [1,3]Room 0 的钥匙打开了 Room 1、3,入栈等待处理。 因为:visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
  4. 4访问 Room 3,钥匙 [0]Room 3 的钥匙对应房间都访问过,visited 把它们挡住,跳过。 因为:visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
  5. 5预测下一步先不要看下一帧——根据当前不变量,预测算法接下来会怎么动。 因为:主动预测会暴露你对不变量的真实理解,比被动看动画有效得多。
  6. 6访问 Room 1,钥匙 [3,0,1]Room 1 的钥匙对应房间都访问过,visited 把它们挡住,跳过。 因为:visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
  7. 7Room 2 永远到不了遍历结束后扫描 visited,判断能否进入所有房间。 因为:有向图可达性:从 0 能走到每个节点则返回 true。
  8. 8收尾与复杂度遍历结束后 visited 全 true 返回 true,否则 false。 因为:时间 O(n + m) · 空间 O(n)。把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。
动画 5 · 完整执行

从输入完整走到可观察结果

从主例第一帧运行到答案,时间轴始终显示当前状态、因果解释和下一步。

Step 1/80%
题目与输入建立输入、目标与算法心智

rooms=[[1,3],[3,0,1],[2],[0]],0 号房间默认打开

正在加载算法场景...
09交互算法精讲

把动画和 Go 代码逐行对应

代码窗口不会按容易漂移的固定数字行号硬绑动画,而是根据当前语义阶段, 在完整 Go 实现中定位选择、计算、写回或返回分支。当前变量与高亮行一起变化。

动画 6 · 代码映射

让每个动作都落到 Go 分支

重新执行关键分支,只显示当前代码附近窗口,并说明这行为什么在此刻运行。

Step 1/60%
从 0 号房间出发做 DFSstack=[0]; visited[0]=true

rooms=[[1,3],[3,0,1],[2],[0]],0 号房间默认打开

正在加载算法场景...
10交互算法精讲

完整 Go 提交代码与最小测试

完整 Go 解法
func canVisitAllRooms(rooms [][]int) bool {
	visited := make(map[int]bool)
	visited[0] = true
	dfsVisitAllRooms(rooms, visited, 0)
	return len(rooms) == len(visited)
}

func dfsVisitAllRooms(es [][]int, visited map[int]bool, from int) {
	for _, to := range es[from] {
		if visited[to] {
			continue
		}
		visited[to] = true
		dfsVisitAllRooms(es, visited, to)
	}
}
最小测试集合
func main() {
    // 1. 主例
    //    输入:mode="keys-and-rooms", rooms=[[1,3],[3,0,1],[2],[0]]
    //    期望:遍历结束后 visited 全 true 返回 true,否则 false。
    //
    // 2. 失败 / 未命中
    //    检查:钥匙在房间内,rooms[i] 的每个值都是一条 i → key 的有向边。
    //
    // 3. 边界
    //    空输入、单元素、最小合法规模,以及答案恰好落在边界的情况。
    //
    // 4. 迁移
    //    LC207 课程表 · 有向图拓扑;LC547 省份数量 · 无向图连通块;LC1971 寻找图中是否存在路径
}

Go 参考实现基于 halfrost/LeetCode-Go 的 MIT 许可代码整理,并按本站教学结构补充解释与动画映射。

11交互算法精讲

正确性与复杂度

时间复杂度 O(n + m)

执行过程中只保留仍可能影响答案的状态。把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。

空间复杂度 O(n)

额外状态主要用于维护:stack/queue 存待处理房间,visited 数组记录哪些房间已打开。

终局不变量
  • 从 0 号节点出发的有向图可达性问题,不要求回到 0。
  • visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
12交互算法精讲

最容易写错的地方

错误 1

钥匙在房间内,rooms[i] 的每个值都是一条 i → key 的有向边。

错误 2

空间不是 O(1):visited 数组需要 O(n)。

边界复查

必须额外检查空输入、单元素、未命中或不可达情况,以及恰好落在边界的输入。

13交互算法精讲

最后复盘:带走逻辑链

  1. 1题意你有 n 个房间,一开始只有 0 号房间是打开的。每个房间里可能有一些钥匙,房间 i 里的钥匙 x 表示可以打开 x 号房间。问题是:从 0 号房间出发,最后能不能进入所有房间?
  2. 2重复重复从多个位置搜索会反复访问同一状态;统一的 visited 与前线结构让每个状态只处理一次。
  3. 3优化把房间看成节点、钥匙看成有向边,从 0 出发做 DFS/BFS,用 visited 标记访问过的房间,避免重复访问和环。
  4. 4证明从 0 号节点出发的有向图可达性问题,不要求回到 0。;visited 防止重复访问同一个房间,也避免房间互相有钥匙时死循环。
  5. 5复杂度时间 O(n + m),空间 O(n)
面试表达:建模成有向图可达性:房间是节点、钥匙是有向边,从 0 做 DFS/BFS,用 visited 避免重复访问和环,最后看 visited 是否全 true。时间 O(n+m),空间 O(n)。
迁移练习
  • LC207 课程表 · 有向图拓扑
  • LC547 省份数量 · 无向图连通块
  • LC1971 寻找图中是否存在路径