841. 钥匙和房间(Keys and Rooms)

频次 ★★★ · 难度 🟡 · 高频:字节

题目

有 N 个房间,从 0 号开始。每个房间有若干钥匙,每把钥匙可以打开对应房间。判断是否能进入所有房间。

示例

输入: rooms = [[1],[2],[3],[]]
输出: true  (0→1→2→3)
输入: rooms = [[1,3],[3,0,1],[2],[0]]
输出: false  (无法进入 2)

思路

DFS/BFS 遍历:从房间 0 出发,用 visited 数组记录已访问房间。每次进入一个房间,拿到钥匙后去开对应房间。DFS 递归或 BFS 队列均可。

本质是图的遍历:房间是节点,钥匙是有向边。

代码

// DFS
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
    boolean[] visited = new boolean[rooms.size()];
    dfs(rooms, 0, visited);
    for (boolean v : visited) {
        if (!v) return false;
    }
    return true;
}
 
private void dfs(List<List<Integer>> rooms, int room, boolean[] visited) {
    visited[room] = true;
    for (int key : rooms.get(room)) {
        if (!visited[key]) {
            dfs(rooms, key, visited);
        }
    }
}
 
// BFS
public boolean canVisitAllRooms(List<List<Integer>> rooms) {
    int n = rooms.size();
    boolean[] visited = new boolean[n];
    Queue<Integer> q = new LinkedList<>();
    q.offer(0);
    visited[0] = true;
    int count = 1;
    while (!q.isEmpty()) {
        int room = q.poll();
        for (int key : rooms.get(room)) {
            if (!visited[key]) {
                visited[key] = true;
                q.offer(key);
                count++;
            }
        }
    }
    return count == n;
}

复杂度

  • 时间:O(N + E),N = 房间数,E = 钥匙总数
  • 空间:O(N)

边界条件

  • 只有一个房间:返回 true
  • 房间 0 无钥匙但其他房间不可达:返回 false
  • 有环(A→B, B→A):visited 防止重复访问

变式

易错点

  • visited 必须在入队/递归前标记,避免重复入队
  • 本质是有向图的连通性判断——DFS/BFS 均可
  • 不需要显式建图,rooms.get(room) 就是邻接表

面试追问

  • BFS 和 DFS 哪个好? 都行,时间复杂度相同。DFS 写起来更短,BFS 可以统计访问计数提前判断
  • 如果房间数量很大? 迭代 BFS 比递归 DFS 更安全(避免栈溢出)

关联题