LeetCode 2061 - Number of Spaces Cleaning Robot Cleaned
In this problem, we are given a 2D binary matrix called room. Each cell represents a position in the room: - 0 means the space is empty and can be cleaned. - 1 means the space contains an object and cannot be entered.
Difficulty: 🟡 Medium
Topics: Array, Matrix, Simulation
Solution
Problem Understanding
In this problem, we are given a 2D binary matrix called room. Each cell represents a position in the room:
0means the space is empty and can be cleaned.1means the space contains an object and cannot be entered.
A cleaning robot starts at the top-left corner (0, 0), and the problem guarantees that this starting position is always empty.
The robot initially faces to the right. It follows a very strict movement rule:
- Move forward in the current direction as long as the next cell is valid and empty.
- If the next move would either:
- go outside the room boundaries, or
- hit an object (
1),
then the robot does not move forward. Instead, it rotates 90 degrees clockwise and tries again.
The robot continues forever. Every empty space it visits becomes cleaned.
The task is to return the total number of unique spaces that become cleaned over the infinite execution of the robot.
The important detail is that the robot may enter a cycle. Since the robot runs indefinitely, eventually its state must repeat. Once the exact same state repeats, the robot will continue behaving identically forever, so no new spaces will ever be cleaned after that point.
The constraints are:
1 <= m, n <= 300- The grid may contain up to
90,000cells. - The robot can revisit cells many times.
These constraints tell us that we need an efficient simulation. A naive infinite simulation is impossible because the robot never stops by itself.
Several edge cases are important:
- A room with only one cell.
- A room where the robot immediately becomes trapped.
- A room with no obstacles.
- Situations where the robot revisits the same cell but with a different direction, because direction affects future movement.
- Large rooms where repeated simulation without cycle detection would run forever.
Approaches
Brute Force Approach
The most straightforward idea is to simulate the robot indefinitely while tracking cleaned cells.
At every step:
- Mark the current cell as cleaned.
- Attempt to move forward.
- If blocked, rotate clockwise.
The problem with this approach is that the robot never terminates on its own. Since the robot runs forever, the simulation would also run forever unless we detect repetition.
A naive brute-force implementation might attempt to stop after some arbitrary number of moves, but that is not reliable because different rooms may require very different numbers of steps before entering a cycle.
Therefore, pure brute force without cycle detection is not practical.
Key Insight
The critical observation is that the robot's future behavior depends entirely on its current state:
- current row
- current column
- current direction
If the robot ever reaches the exact same (row, column, direction) again, the future sequence of moves will repeat forever.
This means we only need to simulate until we revisit a previously seen state.
Since there are:
m * npossible positions4possible directions
there are at most 4 * m * n unique states.
Once all reachable states are exhausted, a cycle must occur.
This transforms the infinite simulation into a finite simulation problem.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Infinite | O(m × n) | Cannot reliably terminate |
| Optimal | O(m × n) | O(m × n) | Uses state cycle detection |
Algorithm Walkthrough
Step 1: Define directions
The robot rotates clockwise, so we store the four directions in order:
- Right
- Down
- Left
- Up
We can represent them as:
directions = [(0,1), (1,0), (0,-1), (-1,0)]
The direction index determines where the robot faces.
Step 2: Track visited states
We create a set called visited_states.
Each state contains:
- row
- column
- direction
For example:
(row, col, direction)
This is necessary because visiting the same cell with a different direction is not the same situation. The robot may behave differently depending on orientation.
Step 3: Track cleaned cells
We also maintain a set called cleaned.
Every time the robot visits an empty cell, we add it to this set.
At the end, the answer is simply:
len(cleaned)
Step 4: Simulate movement
At each iteration:
- Check whether the current state has already been seen.
- If yes, stop simulation because the robot has entered a cycle.
- Otherwise:
- add state to
visited_states - add current cell to
cleaned
Then compute the next position.
Step 5: Handle movement or turning
If the next position:
- is outside bounds, or
- contains an obstacle,
then rotate clockwise:
direction = (direction + 1) % 4
Otherwise, move into the next cell.
Step 6: Return result
Once a repeated state appears, no new cells can ever be cleaned.
Return:
len(cleaned)
Why it works
The algorithm works because the robot is deterministic. Given the same position and direction, it will always make the same future decisions.
Therefore, once a state repeats, the entire future execution repeats as well. No unseen cells can ever be reached after that point.
Since there are finitely many possible states, the simulation must eventually terminate.
Python Solution
from typing import List, Set, Tuple
class Solution:
def numberOfCleanRooms(self, room: List[List[int]]) -> int:
rows = len(room)
cols = len(room[0])
# Right, Down, Left, Up
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
row = 0
col = 0
direction = 0
visited_states: Set[Tuple[int, int, int]] = set()
cleaned: Set[Tuple[int, int]] = set()
while (row, col, direction) not in visited_states:
visited_states.add((row, col, direction))
cleaned.add((row, col))
dr, dc = directions[direction]
next_row = row + dr
next_col = col + dc
# Turn clockwise if blocked
if (
next_row < 0
or next_row >= rows
or next_col < 0
or next_col >= cols
or room[next_row][next_col] == 1
):
direction = (direction + 1) % 4
else:
row = next_row
col = next_col
return len(cleaned)
The implementation closely follows the simulation process described earlier.
First, we store the four directions in clockwise order. This allows rotation using simple modular arithmetic.
The variables row, col, and direction represent the robot's current state.
Two sets are maintained:
visited_statestracks(row, col, direction)cleanedtracks unique cleaned cells
The loop continues until a repeated state appears.
Inside the loop:
- The current state is recorded.
- The current cell is marked as cleaned.
- The next position is computed based on direction.
- If movement is impossible, the robot rotates clockwise.
- Otherwise, the robot advances forward.
Finally, the size of the cleaned set is returned.
Go Solution
func numberOfCleanRooms(room [][]int) int {
rows := len(room)
cols := len(room[0])
// Right, Down, Left, Up
directions := [][]int{
{0, 1},
{1, 0},
{0, -1},
{-1, 0},
}
row, col, direction := 0, 0, 0
visitedStates := make(map[[3]int]bool)
cleaned := make(map[[2]int]bool)
for !visitedStates[[3]int{row, col, direction}] {
visitedStates[[3]int{row, col, direction}] = true
cleaned[[2]int{row, col}] = true
dr := directions[direction][0]
dc := directions[direction][1]
nextRow := row + dr
nextCol := col + dc
if nextRow < 0 ||
nextRow >= rows ||
nextCol < 0 ||
nextCol >= cols ||
room[nextRow][nextCol] == 1 {
direction = (direction + 1) % 4
} else {
row = nextRow
col = nextCol
}
}
return len(cleaned)
}
The Go implementation follows the same logic as the Python solution.
The main difference is that Go does not have built-in tuple types, so fixed-size arrays are used as map keys:
[3]intfor robot states[2]intfor cleaned cells
Go maps naturally support these fixed-size arrays as hashable keys, making them a clean substitute for Python tuples.
No special overflow handling is needed because all indices remain within grid bounds.
Worked Examples
Example 1
Input:
room = [
[0,0,0],
[1,1,0],
[0,0,0]
]
Initial state:
- Position:
(0,0) - Direction: Right
| Step | Position | Direction | Action | Cleaned |
|---|---|---|---|---|
| 1 | (0,0) | Right | Move to (0,1) | {(0,0)} |
| 2 | (0,1) | Right | Move to (0,2) | {(0,0),(0,1)} |
| 3 | (0,2) | Right | Boundary hit, turn down | {(0,0),(0,1),(0,2)} |
| 4 | (0,2) | Down | Move to (1,2) | same |
| 5 | (1,2) | Down | Move to (2,2) | add (1,2) |
| 6 | (2,2) | Down | Boundary hit, turn left | add (2,2) |
| 7 | (2,2) | Left | Move to (2,1) | same |
| 8 | (2,1) | Left | Move to (2,0) | add (2,1) |
| 9 | (2,0) | Left | Boundary hit, turn up | add (2,0) |
Eventually the robot repeats a previous state.
Total cleaned cells:
7
Example 2
Input:
room = [
[0,1,0],
[1,0,0],
[0,0,0]
]
| Step | Position | Direction | Action |
|---|---|---|---|
| 1 | (0,0) | Right | Obstacle, turn down |
| 2 | (0,0) | Down | Obstacle, turn left |
| 3 | (0,0) | Left | Boundary, turn up |
| 4 | (0,0) | Up | Boundary, turn right |
Now the robot returns to:
(0,0,Right)
This state was already visited, so the simulation stops.
Only one cell was cleaned.
Answer:
1
Example 3
Input:
room = [
[0,0,0],
[0,0,0],
[0,0,0]
]
The robot traverses the outer perimeter continuously.
The center cell (1,1) is never visited because the robot only turns when blocked.
Cleaned cells:
8
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m × n) | Each state is processed at most once |
| Space | O(m × n) | Stores visited states and cleaned cells |
There are at most 4 * m * n possible robot states because each cell can be visited facing four different directions.
The simulation terminates immediately after a repeated state is encountered, so every state is processed at most once.
The cleaned set and visited state set both scale linearly with the number of cells.
Test Cases
from typing import List
class Solution:
def numberOfCleanRooms(self, room: List[List[int]]) -> int:
rows = len(room)
cols = len(room[0])
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
row = 0
col = 0
direction = 0
visited_states = set()
cleaned = set()
while (row, col, direction) not in visited_states:
visited_states.add((row, col, direction))
cleaned.add((row, col))
dr, dc = directions[direction]
next_row = row + dr
next_col = col + dc
if (
next_row < 0
or next_row >= rows
or next_col < 0
or next_col >= cols
or room[next_row][next_col] == 1
):
direction = (direction + 1) % 4
else:
row = next_row
col = next_col
return len(cleaned)
sol = Solution()
assert sol.numberOfCleanRooms([[0,0,0],[1,1,0],[0,0,0]]) == 7
# example 1
assert sol.numberOfCleanRooms([[0,1,0],[1,0,0],[0,0,0]]) == 1
# example 2
assert sol.numberOfCleanRooms([[0,0,0],[0,0,0],[0,0,0]]) == 8
# example 3
assert sol.numberOfCleanRooms([[0]]) == 1
# single cell room
assert sol.numberOfCleanRooms([[0,0,0,0]]) == 4
# single row
assert sol.numberOfCleanRooms([[0],[0],[0],[0]]) == 4
# single column
assert sol.numberOfCleanRooms([[0,1],[1,0]]) == 1
# trapped immediately
assert sol.numberOfCleanRooms([[0,0],[0,0]]) == 4
# small open square
assert sol.numberOfCleanRooms([[0,1,0],[0,1,0],[0,0,0]]) == 5
# obstacle corridor
assert sol.numberOfCleanRooms([[0,0,1],[1,0,1],[0,0,0]]) >= 1
# irregular obstacle pattern
Test Summary
| Test | Why |
|---|---|
| Example 1 | Standard turning behavior |
| Example 2 | Immediate trapped cycle |
| Example 3 | Open grid perimeter traversal |
| Single cell | Smallest possible input |
| Single row | Horizontal boundary handling |
| Single column | Vertical boundary handling |
| Trapped immediately | Repeated state detection |
| Small open square | Full traversal of small grid |
| Obstacle corridor | Turning around obstacles |
| Irregular obstacles | General robustness |
Edge Cases
Single Cell Room
A 1 x 1 room is the smallest possible input. The robot starts at (0,0) and cannot move in any direction because every move hits a boundary.
A buggy implementation might accidentally loop forever without detecting repeated directional states. Our implementation correctly stores (row, col, direction) states, so after cycling through all four directions, the repeated state is detected.
Revisiting the Same Cell with Different Directions
A very common mistake is tracking only visited positions instead of full states.
For example, the robot may revisit (2,3) while facing a different direction. The future movement from that configuration may be completely different.
Our implementation avoids this bug by storing:
(row, col, direction)
instead of just (row, col).
Completely Open Grid
In an empty grid, the robot does not necessarily visit every cell.
For example, in a 3 x 3 empty room, the robot only traverses the perimeter and never enters the center.
A naive assumption that every empty cell gets cleaned would be incorrect. Our simulation follows the exact movement rules, so only genuinely visited cells are counted.