Keys and Rooms
MedPrompt
There are n rooms labeled from 0 to n - 1 and all the rooms are locked except for room 0. Your goal is to visit all the rooms. However, you cannot enter a locked room without having its key.
When you visit a room, you may find a set of distinct keys in it. Each key has a number on it, denoting which room it unlocks, and you can take all of them with you to unlock the other rooms.
Given an array rooms where rooms[i] is the set of keys that you can obtain if you visited room i, return true if you can visit all the rooms, or false otherwise.
Example 1:
Input: rooms = [[1],[2],[3],[]]
Output: true
Explanation:
We visit room 0 and pick up key 1.
We then visit room 1 and pick up key 2.
We then visit room 2 and pick up key 3.
We then visit room 3.
Since we were able to visit every room, we return true.Example 2:
Input: rooms = [[1,3],[3,0,1],[2],[0]]
Output: false
Explanation: We can not enter room number 2 since the only key that unlocks it is in that room.
Constraints:
n == rooms.length2 <= n <= 10000 <= rooms[i].length <= 10001 <= sum(rooms[i].length) <= 30000 <= rooms[i][j] < n- All the values of
rooms[i]are unique.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach simulates the process of collecting keys and visiting rooms in a straightforward, iterative manner. It maintains a set of visited rooms and a set of collected keys. It repeatedly tries to use the collected keys to visit new rooms and gather more keys, continuing this process until no new rooms can be accessed. This method is less efficient than standard graph traversal algorithms because it may re-process keys unnecessarily.
Algorithm
- Initialize a
Set<Integer>namedvisitedand add0to it. - Initialize a
Set<Integer>namedkeysand add all keys fromrooms.get(0). - Start a
whileloop that continues as long as progress is made in visiting new rooms. - Inside the loop, set a
progressMadeflag tofalse. - Create a temporary copy of the
keysset to iterate over, to avoid modification issues while iterating. - For each
keyin the copied set:- If the room corresponding to
keyhas not been visited:- Add the
keyto thevisitedset. - Add all keys from
rooms.get(key)to a temporarynewKeysFoundset. - Set
progressMadetotrue.
- Add the
- If the room corresponding to
- After the inner loop, add all keys from
newKeysFoundto the mainkeysset. - If
progressMadeisfalseafter a full iteration, break thewhileloop. - After the loop terminates, return
trueifvisited.size()equals the total number of rooms,falseotherwise.
Walkthrough
We use a Set called visited to store the indices of rooms we have entered, and another Set called keys to store all the keys we have collected so far. Initially, we can only enter room 0, so we add 0 to visited and collect all keys from rooms[0], adding them to the keys set. We then enter a loop that continues as long as we are making progress (i.e., visiting new rooms). Inside the loop, we iterate through a copy of our current keys. For each key, if it unlocks a room we haven't visited, we mark that room as visited, collect all the keys from it, and add them to our keys set. If an entire pass through our collected keys results in no new rooms being visited, we know we are stuck and can't explore further, so we break the loop. Finally, we check if the number of rooms in our visited set is equal to the total number of rooms.
import java.util.HashSet;import java.util.Set;import java.util.List;import java.util.ArrayList; class Solution { public boolean canVisitAllRooms(List<List<Integer>> rooms) { int n = rooms.size(); Set<Integer> visited = new HashSet<>(); Set<Integer> keys = new HashSet<>(); // Start at room 0 visited.add(0); keys.addAll(rooms.get(0)); boolean progressMade = true; while (progressMade) { progressMade = false; Set<Integer> newKeysFound = new HashSet<>(); // Iterate over a copy to avoid ConcurrentModificationException List<Integer> keysToTry = new ArrayList<>(keys); for (int key : keysToTry) { if (!visited.contains(key)) { visited.add(key); newKeysFound.addAll(rooms.get(key)); progressMade = true; } } keys.addAll(newKeysFound); } return visited.size() == n; }}Complexity
Time
O(N * E), where N is the number of rooms and E is the total number of keys. In the worst-case scenario, we might only visit one new room per iteration of the outer `while` loop, leading to N iterations. In each iteration, we iterate through all collected keys, which can be up to E keys in total. This leads to a significantly worse performance compared to linear time graph traversals.
Space
O(N + E) to store the `visited` set (size up to N) and the `keys` set (size up to E, the total number of keys).
Trade-offs
Pros
Relatively easy to understand as it directly simulates the physical process of collecting keys and opening doors.
Cons
Inefficient due to repeated processing of keys.
The implementation is more complex and less clean than standard graph traversal algorithms.
This approach models the problem as a graph traversal. Each room is a node, and the keys are directed edges. We start a Depth-First Search (DFS) from room 0 to find all reachable rooms. After the traversal, we check if the number of visited rooms equals the total number of rooms. This is an efficient and standard way to solve connectivity problems in graphs.
Algorithm
- Initialize a boolean array
visitedof sizen(number of rooms) to allfalse. - Define a recursive function
dfs(room, rooms, visited). - Call
dfs(0, rooms, visited)to start the traversal from room 0. - Inside
dfs(room, ...):- Mark the current
roomas visited:visited[room] = true. - Get the list of keys for the current
room. - For each
keyin the list:- If the room
keyhas not been visited (!visited[key]), recursively calldfs(key, rooms, visited).
- If the room
- Mark the current
- After the initial
dfscall completes, iterate through thevisitedarray. - If any element
visited[i]isfalse, returnfalse. - If the loop completes without finding any unvisited rooms, return
true.
Walkthrough
The problem can be seen as finding if all nodes in a directed graph are reachable from a starting node (room 0). DFS is a natural fit for this. We use a boolean array visited to keep track of the rooms we have entered. We can implement DFS either recursively or iteratively using a stack. The recursive approach is often more concise. We start the dfs function from room 0. In the function, we first mark the current room as visited. Then, for each key in the current room, we check if the room it unlocks has been visited. If not, we make a recursive call to dfs for that new room. This process continues until we have explored all reachable rooms from room 0. After the initial dfs(0) call returns, we iterate through our visited array. If any room is marked as false, it means it was unreachable, so we return false. If all rooms are true, we return true.
Recursive DFS:
import java.util.List; class Solution { public boolean canVisitAllRooms(List<List<Integer>> rooms) { boolean[] visited = new boolean[rooms.size()]; dfs(0, rooms, visited); for (boolean roomVisited : visited) { if (!roomVisited) { return false; } } return true; } private void dfs(int room, List<List<Integer>> rooms, boolean[] visited) { if (visited[room]) { return; } visited[room] = true; for (int key : rooms.get(room)) { dfs(key, rooms, visited); } }}Iterative DFS:
import java.util.List;import java.util.Stack; class SolutionIterative { public boolean canVisitAllRooms(List<List<Integer>> rooms) { boolean[] visited = new boolean[rooms.size()]; Stack<Integer> stack = new Stack<>(); stack.push(0); visited[0] = true; int count = 1; while (!stack.isEmpty()) { int u = stack.pop(); for (int v : rooms.get(u)) { if (!visited[v]) { visited[v] = true; stack.push(v); count++; } } } return count == rooms.size(); }}Complexity
Time
O(N + E), where N is the number of rooms and E is the total number of keys. In the worst case, we visit each room (node) and each key (edge) exactly once.
Space
O(N) for the `visited` array. In the recursive approach, the space complexity also includes the recursion stack, which can be O(N) in the worst case (e.g., a graph where rooms form a single long chain). The iterative approach uses a stack that can also grow to O(N).
Trade-offs
Pros
Efficient and optimal solution.
Standard, well-understood algorithm for graph traversal.
The recursive implementation is very concise and elegant.
Cons
The recursive version could theoretically cause a stack overflow if the graph is extremely deep, though this is not an issue with the given constraints (N <= 1000).
This approach also treats the problem as a graph traversal, but uses Breadth-First Search (BFS) instead of DFS. Starting from room 0, it explores rooms level by level. It uses a queue to manage the rooms to visit. This method is equally efficient as DFS and is another standard technique for solving graph connectivity problems.
Algorithm
- Initialize a boolean array
visitedof sizento allfalse. - Initialize a
Queue<Integer>and add0to it. - Mark room 0 as visited:
visited[0] = true. - Initialize a counter
visitedCountto 1. - While the queue is not empty:
- Dequeue a room, let's call it
currentRoom. - For each
keyinrooms.get(currentRoom):- If room
keyhas not been visited (!visited[key]):- Mark it as visited:
visited[key] = true. - Enqueue the
key. - Increment
visitedCount.
- Mark it as visited:
- If room
- Dequeue a room, let's call it
- After the loop, return
trueifvisitedCountequalsn,falseotherwise.
Walkthrough
BFS explores the graph layer by layer from the source node (room 0). This is achieved using a queue data structure. We initialize a Queue and add room 0 to it. We also use a visited array (or set) to avoid processing rooms more than once and to prevent cycles. We mark room 0 as visited and add it to the queue. We also keep a count of visited rooms. The main loop continues as long as the queue is not empty. In each iteration, we dequeue a room. For this room, we iterate through all the keys it contains. For each key, if the corresponding room has not been visited, we mark it as visited, increment our count, and enqueue it. After the loop finishes, all reachable rooms will have been visited. We simply compare our count of visited rooms with the total number of rooms (n). If they match, it means all rooms were reachable.
import java.util.List;import java.util.Queue;import java.util.LinkedList; class Solution { public boolean canVisitAllRooms(List<List<Integer>> rooms) { int n = rooms.size(); boolean[] visited = new boolean[n]; Queue<Integer> queue = new LinkedList<>(); // Start with room 0 visited[0] = true; queue.add(0); int visitedCount = 1; while (!queue.isEmpty()) { int currentRoom = queue.poll(); for (int key : rooms.get(currentRoom)) { if (!visited[key]) { visited[key] = true; queue.add(key); visitedCount++; } } } return visitedCount == n; }}Complexity
Time
O(N + E), where N is the number of rooms and E is the total number of keys. Each room is enqueued and dequeued once, and each key is checked once.
Space
O(N) for the `visited` array. The space for the queue can also be up to O(N) in the worst case (e.g., a star graph where room 0 has keys to all other rooms).
Trade-offs
Pros
Efficient and optimal solution.
Iterative by nature, so it avoids recursion depth limits.
Finds the shortest path in terms of edges (number of rooms visited) to any node, although not required here.
Cons
Generally requires slightly more code than a recursive DFS.
No significant performance difference from DFS for this problem.
Solutions
Solution
class Solution { private List < List < Integer >> rooms ; private Set < Integer > vis ; public boolean canVisitAllRooms ( List < List < Integer >> rooms ) { vis = new HashSet <>(); this . rooms = rooms ; dfs ( 0 ); return vis . size () == rooms . size (); } private void dfs ( int u ) { if ( vis . contains ( u )) { return ; } vis . add ( u ); for ( int v : rooms . get ( u )) { dfs ( v ); } } }class Solution { public: vector < vector < int >> rooms ; unordered_set < int > vis ; bool canVisitAllRooms ( vector < vector < int >>& rooms ) { vis . clear (); this -> rooms = rooms ; dfs ( 0 ); return vis . size () == rooms . size (); } void dfs ( int u ) { if ( vis . count ( u )) return ; vis . insert ( u ); for ( int v : rooms [ u ]) dfs ( v ); } };class Solution : def canVisitAllRooms ( self , rooms : List [ List [ int ]]) -> bool : def dfs ( u ): if u in vis : return vis . add ( u ) for v in rooms [ u ]: dfs ( v ) vis = set () dfs ( 0 ) return len ( vis ) == len ( rooms )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.