489. Robot Room Cleaner 🔒
Problem Description
You need to control a robot to clean all accessible cells in a room. The room is represented as an m x n
grid where 1
represents an empty cell that needs cleaning and 0
represents a wall that blocks movement.
The challenge is that you don't know:
- The room's layout
- The robot's starting position
- The size of the room
You can only interact with the robot through these four APIs:
move()
: Attempts to move the robot forward one cell. Returnstrue
if successful (cell is empty),false
if blocked by a wallturnLeft()
: Rotates the robot 90 degrees counter-clockwise without movingturnRight()
: Rotates the robot 90 degrees clockwise without movingclean()
: Cleans the current cell where the robot is standing
Key constraints:
- The robot starts facing upward
- The robot begins at an empty cell (guaranteed)
- All edges of the grid are surrounded by walls
- You must clean every reachable empty cell in the room
The solution uses a depth-first search (DFS) approach with backtracking. Starting from position (0, 0)
as a relative coordinate system, the algorithm:
- Marks the current cell as visited and cleans it
- Tries to explore all 4 directions (up, right, down, left) by rotating
- For each direction, if the cell hasn't been visited and the robot can move there, recursively explores that path
- After exploring a direction, backtracks to the original position and orientation by turning 180 degrees, moving back, then turning 180 degrees again
- Continues rotating to try the next direction
The dirs
array (-1, 0, 1, 0, -1)
represents direction vectors where consecutive pairs form (row_offset, col_offset) for up, right, down, and left movements. The visited set vis
keeps track of cleaned cells using relative coordinates to avoid revisiting them.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- No: While the room is a grid, we're not dealing with explicit nodes and edges. We're exploring an unknown space cell by cell.
Need to solve for kth smallest/largest?
- No: We're not looking for any kth element. We need to visit and clean all reachable cells.
Involves Linked Lists?
- No: The problem doesn't involve linked list data structures.
Does the problem have small constraints?
- Yes: Although the exact grid size is unknown, the problem involves exploring all possible paths from the current position. We need to systematically try all directions (4 possible moves at each cell) and explore all reachable cells. The constraint is that we must explore exhaustively without knowing the room layout beforehand.
Brute force / Backtracking?
- Yes: This is a classic backtracking problem. We need to:
- Try moving in each direction from the current cell
- Recursively explore new cells when we can move there
- Backtrack to the previous position after exploring a path
- Continue trying other directions until all possibilities are exhausted
Conclusion: The flowchart correctly leads us to use a Backtracking approach. The robot must systematically explore all possible paths, clean each cell it visits, and backtrack when it hits dead ends or already-visited cells. This ensures complete coverage of all reachable empty cells in the room.
Intuition
The key insight is that we're essentially blindfolded - we don't know where we are or what the room looks like. This is similar to solving a maze in complete darkness where you can only feel if there's a wall in front of you.
Think of it like being in a dark room with a flashlight that only shows the spot you're standing on. You need to clean every spot you can reach, but you can't see the whole room at once. The natural approach is to:
- Clean where you're standing
- Try to move forward
- If you can move, explore that new area completely
- When done exploring, come back to where you were
- Try a different direction
This naturally leads to a depth-first exploration pattern. Why DFS over BFS? Because with the robot's limited API, it's much easier to backtrack one step at a time (going back the way we came) than to jump between different branches of exploration.
The crucial realization is that we need to maintain our orientation. When the robot moves forward and explores a new area, it might turn multiple times. To properly backtrack, we need to:
- Turn 180 degrees (two right turns)
- Move back one step
- Turn 180 degrees again to restore the original orientation
This ensures that when we return from exploring one direction, we're in the exact same position and facing the same way, ready to try the next direction.
We use relative coordinates (0, 0)
as our starting point since we don't know our absolute position. By tracking visited cells in this relative coordinate system and systematically trying all four directions from each cell, we guarantee that every reachable cell will be cleaned exactly once.
The pattern of trying all 4 directions is achieved by rotating right after each attempt, effectively scanning 360 degrees from each position. This systematic rotation combined with recursive exploration and proper backtracking ensures complete coverage of the room.
Solution Approach
The implementation uses DFS with backtracking to systematically explore and clean the entire room. Let's break down the key components:
Data Structures:
vis
: A set storing visited cells as tuples(i, j)
using relative coordinatesdirs
: An array(-1, 0, 1, 0, -1)
encoding direction vectors where consecutive pairs represent (row_offset, col_offset)
Direction Encoding: The robot faces one of four directions, encoded as:
d = 0
: Up (move by(-1, 0)
)d = 1
: Right (move by(0, 1)
)d = 2
: Down (move by(1, 0)
)d = 3
: Left (move by(0, -1)
)
Core Algorithm - DFS Function:
def dfs(i, j, d):
Takes current position (i, j)
and direction d
the robot is facing.
-
Mark and Clean: Add current position to
vis
and clean the cellvis.add((i, j)) robot.clean()
-
Explore All 4 Directions: Loop through 4 possible moves
for k in range(4): nd = (d + k) % 4
k = 0
: Try current directionk = 1
: Try after one right turnk = 2
: Try after two right turnsk = 3
: Try after three right turns
-
Calculate Next Position:
x, y = i + dirs[nd], j + dirs[nd + 1]
Using the direction index
nd
to get the appropriate offset fromdirs
. -
Move and Explore: If cell is unvisited and movement succeeds:
if (x, y) not in vis and robot.move(): dfs(x, y, nd)
Recursively explore from the new position with updated direction.
-
Backtrack: After exploring, return to original position:
robot.turnRight() robot.turnRight() robot.move() robot.turnRight() robot.turnRight()
- First two right turns: Face opposite direction (180°)
- Move back one cell
- Two more right turns: Restore original orientation
-
Rotate for Next Direction:
robot.turnRight()
Turn right to try the next direction in the loop.
Initialization:
Start DFS from relative position (0, 0)
facing up (direction 0
):
dfs(0, 0, 0)
The algorithm guarantees that every reachable cell is visited exactly once through the combination of:
- Systematic exploration of all 4 directions from each cell
- Tracking visited cells to avoid redundant cleaning
- Proper backtracking to maintain position and orientation consistency
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate how the DFS with backtracking solution works. Consider a simple 3x3 room where the robot starts at the center:
Grid (unknown to us): Robot's view: # # # # # Just knows it can # . . . # move or hit walls # . R . # # . . . # R = Robot start # # # # # . = Empty cell # = Wall
The robot starts at what we'll call position (0, 0)
in our relative coordinate system, facing up (direction d = 0
).
Step 1: Initial Position (0, 0)
- Mark
(0, 0)
as visited:vis = {(0, 0)}
- Clean current cell
- Try 4 directions by rotating:
- Direction 0 (Up): Calculate next position
(-1, 0)
. Not visited. Trymove()
→ returnstrue
- Move to
(-1, 0)
- Direction 0 (Up): Calculate next position
Step 2: Position (-1, 0)
- Mark
(-1, 0)
as visited:vis = {(0, 0), (-1, 0)}
- Clean current cell
- Try 4 directions:
- Direction 0 (Up): Try
(-2, 0)
.move()
→ returnsfalse
(wall) - Turn right
- Direction 1 (Right): Try
(-1, 1)
. Not visited.move()
→ returnstrue
- Move to
(-1, 1)
- Direction 0 (Up): Try
Step 3: Position (-1, 1)
- Mark
(-1, 1)
as visited:vis = {(0, 0), (-1, 0), (-1, 1)}
- Clean current cell
- Try 4 directions:
- All directions either hit walls or visited cells
- Backtrack: Turn 180°, move back to
(-1, 0)
, turn 180° to restore orientation - Continue trying remaining directions from
(-1, 0)
Step 4: Back at (-1, 0), continuing exploration
- Turn right (now facing Down)
- Direction 2 (Down):
(0, 0)
already visited, skip - Turn right (now facing Left)
- Direction 3 (Left): Try
(-1, -1)
. Not visited.move()
→ returnstrue
- Move to
(-1, -1)
Step 5: Position (-1, -1)
- Mark and clean
- Explore all directions (all blocked or visited)
- Backtrack to
(-1, 0)
Step 6: Back at (-1, 0), all directions tried
- Backtrack to
(0, 0)
(original position)
Step 7: Back at (0, 0), continue with remaining directions
- We already tried Up (direction 0)
- Turn right (now facing Right)
- Direction 1 (Right): Try
(0, 1)
. Not visited.move()
→ returnstrue
- Continue this pattern...
The process continues until all 5 empty cells are visited and cleaned:
(0, 0)
- center(-1, 0)
- top middle(-1, 1)
- top right(-1, -1)
- top left(0, -1)
- middle left(1, 0)
- bottom middle
The key insights from this walkthrough:
- We systematically try all 4 directions from each position by rotating
- We use relative coordinates since we don't know our absolute position
- After exploring each branch, we carefully backtrack to maintain our position and orientation
- The visited set prevents us from cleaning the same cell twice
- The combination of DFS and systematic rotation ensures complete coverage
Solution Implementation
1class Solution:
2 def cleanRoom(self, robot: 'Robot') -> None:
3 """
4 Clean all reachable cells in the room using DFS exploration.
5
6 :type robot: Robot
7 :rtype: None
8 """
9
10 def backtrack():
11 """Move the robot back to its previous cell and restore original direction."""
12 robot.turnRight()
13 robot.turnRight()
14 robot.move()
15 robot.turnRight()
16 robot.turnRight()
17
18 def depth_first_search(row: int, col: int, direction: int) -> None:
19 """
20 Recursively explore and clean the room using DFS.
21
22 Args:
23 row: Current row position (relative to starting point)
24 col: Current column position (relative to starting point)
25 direction: Current facing direction (0: up, 1: right, 2: down, 3: left)
26 """
27 # Mark current cell as visited and clean it
28 visited_cells.add((row, col))
29 robot.clean()
30
31 # Try all 4 directions (up, right, down, left)
32 for turn_count in range(4):
33 # Calculate new direction after turning right 'turn_count' times
34 new_direction = (direction + turn_count) % 4
35
36 # Calculate next cell coordinates based on new direction
37 next_row = row + direction_deltas[new_direction]
38 next_col = col + direction_deltas[new_direction + 1]
39
40 # If the next cell hasn't been visited and robot can move there
41 if (next_row, next_col) not in visited_cells and robot.move():
42 # Recursively explore from the new cell
43 depth_first_search(next_row, next_col, new_direction)
44 # Backtrack: return to current cell facing original direction
45 backtrack()
46
47 # Turn right to try next direction
48 robot.turnRight()
49
50 # Direction deltas for (row, col) in order: up, right, down, left
51 # Using a circular array pattern where index i gives row delta, i+1 gives col delta
52 direction_deltas = (-1, 0, 1, 0, -1)
53
54 # Set to track visited cells (relative coordinates)
55 visited_cells = set()
56
57 # Start DFS from origin (0, 0) facing up (direction 0)
58 depth_first_search(0, 0, 0)
59
1/**
2 * // This is the robot's control interface.
3 * // You should not implement it, or speculate about its implementation
4 * interface Robot {
5 * // Returns true if the cell in front is open and robot moves into the cell.
6 * // Returns false if the cell in front is blocked and robot stays in the current cell.
7 * public boolean move();
8 *
9 * // Robot will stay in the same cell after calling turnLeft/turnRight.
10 * // Each turn will be 90 degrees.
11 * public void turnLeft();
12 * public void turnRight();
13 *
14 * // Clean the current cell.
15 * public void clean();
16 * }
17 */
18
19class Solution {
20 // Direction vectors: up, right, down, left (clockwise order)
21 // Using pairs: (rowDelta, colDelta) for each direction
22 private int[] directionDeltas = {-1, 0, 1, 0, -1};
23
24 // Set to track visited cells using their coordinates
25 private Set<List<Integer>> visitedCells = new HashSet<>();
26
27 // Robot instance
28 private Robot robot;
29
30 /**
31 * Main method to clean the entire room accessible by the robot
32 * @param robot The robot instance to control
33 */
34 public void cleanRoom(Robot robot) {
35 this.robot = robot;
36 // Start DFS from origin (0, 0) facing up (direction 0)
37 dfs(0, 0, 0);
38 }
39
40 /**
41 * Depth-first search to explore and clean all reachable cells
42 * @param row Current row position (relative to starting point)
43 * @param col Current column position (relative to starting point)
44 * @param direction Current facing direction (0: up, 1: right, 2: down, 3: left)
45 */
46 private void dfs(int row, int col, int direction) {
47 // Clean the current cell
48 robot.clean();
49
50 // Mark current cell as visited
51 visitedCells.add(List.of(row, col));
52
53 // Try all 4 directions (relative to current direction)
54 for (int turn = 0; turn < 4; ++turn) {
55 // Calculate new direction after turning clockwise 'turn' times
56 int newDirection = (direction + turn) % 4;
57
58 // Calculate next cell coordinates based on new direction
59 int nextRow = row + directionDeltas[newDirection];
60 int nextCol = col + directionDeltas[newDirection + 1];
61
62 // Check if next cell is unvisited and accessible
63 if (!visitedCells.contains(List.of(nextRow, nextCol)) && robot.move()) {
64 // Recursively explore the new cell
65 dfs(nextRow, nextCol, newDirection);
66
67 // Backtrack: turn 180 degrees
68 robot.turnRight();
69 robot.turnRight();
70
71 // Move back to previous cell
72 robot.move();
73
74 // Turn 180 degrees again to restore original orientation
75 robot.turnRight();
76 robot.turnRight();
77 }
78
79 // Turn right to try next direction (clockwise)
80 robot.turnRight();
81 }
82 }
83}
84
1/**
2 * // This is the robot's control interface.
3 * // You should not implement it, or speculate about its implementation
4 * class Robot {
5 * public:
6 * // Returns true if the cell in front is open and robot moves into the cell.
7 * // Returns false if the cell in front is blocked and robot stays in the current cell.
8 * bool move();
9 *
10 * // Robot will stay in the same cell after calling turnLeft/turnRight.
11 * // Each turn will be 90 degrees.
12 * void turnLeft();
13 * void turnRight();
14 *
15 * // Clean the current cell.
16 * void clean();
17 * };
18 */
19
20class Solution {
21public:
22 void cleanRoom(Robot& robot) {
23 // Direction vectors: up(0), right(1), down(2), left(3)
24 // Using 5 elements to avoid index out of bounds when accessing dirs[direction + 1]
25 int directions[5] = {-1, 0, 1, 0, -1};
26
27 // Set to track visited cells using their coordinates
28 set<pair<int, int>> visitedCells;
29
30 // DFS function to explore and clean the room
31 // Parameters: row, column, currentDirection (0=up, 1=right, 2=down, 3=left)
32 function<void(int, int, int)> dfs = [&](int row, int col, int currentDirection) {
33 // Clean the current cell
34 robot.clean();
35
36 // Mark current cell as visited
37 visitedCells.insert({row, col});
38
39 // Try all 4 directions (relative to current direction)
40 for (int turn = 0; turn < 4; ++turn) {
41 // Calculate new direction after turning
42 int newDirection = (currentDirection + turn) % 4;
43
44 // Calculate next cell coordinates based on new direction
45 int nextRow = row + directions[newDirection];
46 int nextCol = col + directions[newDirection + 1];
47
48 // If the next cell hasn't been visited and robot can move there
49 if (!visitedCells.count({nextRow, nextCol}) && robot.move()) {
50 // Recursively clean from the new position
51 dfs(nextRow, nextCol, newDirection);
52
53 // Backtrack: turn 180 degrees
54 robot.turnRight();
55 robot.turnRight();
56
57 // Move back to previous cell
58 robot.move();
59
60 // Turn 180 degrees again to restore original orientation
61 robot.turnRight();
62 robot.turnRight();
63 }
64
65 // Turn right to try next direction (clockwise)
66 robot.turnRight();
67 }
68 };
69
70 // Start DFS from initial position (0, 0) facing up (direction = 0)
71 dfs(0, 0, 0);
72 }
73};
74
1/**
2 * Robot Room Cleaner - DFS with Backtracking
3 * The robot starts at an unknown position and needs to clean all accessible cells
4 * Using DFS to explore and backtrack to clean the entire room
5 */
6
7// Direction vectors: up, right, down, left (clockwise order)
8// Using 5 elements where dirs[i] and dirs[i+1] represent x and y offsets
9const DIRECTION_OFFSETS = [-1, 0, 1, 0, -1];
10
11// Set to track visited cells using "x-y" format as key
12const visitedCells = new Set<string>();
13
14/**
15 * Depth-first search to explore and clean the room
16 * @param currentRow - Current row position of the robot
17 * @param currentCol - Current column position of the robot
18 * @param currentDirection - Current facing direction (0: up, 1: right, 2: down, 3: left)
19 */
20function dfsClean(currentRow: number, currentCol: number, currentDirection: number): void {
21 // Mark current cell as visited
22 const cellKey = `${currentRow}-${currentCol}`;
23 visitedCells.add(cellKey);
24
25 // Clean the current cell
26 robot.clean();
27
28 // Try all 4 directions from current position
29 for (let directionAttempt = 0; directionAttempt < 4; directionAttempt++) {
30 // Calculate new direction after turning (staying clockwise)
31 const newDirection = (currentDirection + directionAttempt) % 4;
32
33 // Calculate next cell coordinates based on new direction
34 const nextRow = currentRow + DIRECTION_OFFSETS[newDirection];
35 const nextCol = currentCol + DIRECTION_OFFSETS[newDirection + 1];
36 const nextCellKey = `${nextRow}-${nextCol}`;
37
38 // If cell not visited and robot can move forward
39 if (!visitedCells.has(nextCellKey) && robot.move()) {
40 // Recursively clean from the new position
41 dfsClean(nextRow, nextCol, newDirection);
42
43 // Backtrack: turn 180 degrees, move back, then turn 180 degrees again
44 robot.turnRight();
45 robot.turnRight();
46 robot.move();
47 robot.turnRight();
48 robot.turnRight();
49 }
50
51 // Turn right to try next direction in clockwise order
52 robot.turnRight();
53 }
54}
55
56/**
57 * Main function to clean the room
58 * @param robot - Robot instance with move, turn, and clean methods
59 */
60function cleanRoom(robot: Robot): void {
61 // Start DFS from origin (0, 0) facing up (direction 0)
62 dfsClean(0, 0, 0);
63}
64
Time and Space Complexity
Time Complexity: O(N - M)
where N
is the total number of cells in the room and M
is the number of obstacles.
The algorithm uses DFS to explore all accessible cells in the room. Each accessible cell is visited exactly once due to the visited set tracking. For each cell visit, the robot:
- Cleans the cell:
O(1)
- Attempts to move in 4 directions:
O(4)
=O(1)
- For each direction, performs constant operations (turning, checking, potentially moving and backtracking)
Since we visit each accessible cell once and perform constant work at each cell, the total time complexity is O(N - M)
, which represents the number of cleanable cells.
Space Complexity: O(N - M)
The space complexity consists of:
- Visited Set (
vis
): Stores coordinates of all visited cells. In the worst case, all accessible cells are visited, requiringO(N - M)
space. - Recursion Stack: The DFS recursion can go as deep as the number of accessible cells in the worst case (imagine a long winding path). This also requires
O(N - M)
space in the call stack. - Other Variables: The
dirs
array and other local variables useO(1)
space.
Therefore, the total space complexity is O(N - M)
for storing visited cells and maintaining the recursion stack.
Common Pitfalls
1. Incorrect Backtracking Implementation
One of the most common mistakes is implementing the backtracking incorrectly, which causes the robot to lose its position or orientation.
Pitfall Example:
# Wrong: Only turning once or forgetting to restore orientation
def backtrack():
robot.turnRight()
robot.move()
robot.turnRight() # Missing another turnRight()
Why It Fails: After exploring a path, the robot must return to exactly the same position AND orientation it had before moving. The robot needs to:
- Turn 180° to face the opposite direction
- Move back one cell
- Turn 180° again to restore the original facing direction
Correct Implementation:
def backtrack():
robot.turnRight() # Turn 90°
robot.turnRight() # Turn another 90° (now facing opposite)
robot.move() # Move back to previous cell
robot.turnRight() # Turn 90°
robot.turnRight() # Turn another 90° (restored original direction)
2. Forgetting to Turn Right After Each Direction Attempt
Another critical mistake is forgetting to turn right after checking each direction in the loop.
Pitfall Example:
for k in range(4):
nd = (d + k) % 4
x, y = i + dirs[nd], j + dirs[nd + 1]
if (x, y) not in vis and robot.move():
dfs(x, y, nd)
backtrack()
# Missing robot.turnRight() here!
Why It Fails: The algorithm assumes that for each iteration k
, the robot has already turned right k
times from its initial direction. Without the turn at the end of each iteration, the robot's actual orientation won't match the calculated direction nd
.
Correct Implementation:
for k in range(4):
nd = (d + k) % 4
x, y = i + dirs[nd], j + dirs[nd + 1]
if (x, y) not in vis and robot.move():
dfs(x, y, nd)
backtrack()
robot.turnRight() # Essential: Turn right to try next direction
3. Incorrect Direction Vector Calculation
Using the wrong indices when accessing the direction deltas array.
Pitfall Example:
# Wrong: Using nd twice instead of nd and nd+1 x, y = i + dirs[nd], j + dirs[nd] # This would use the same value for both
Why It Fails: The dirs
array stores alternating row and column offsets. For direction nd
, the row offset is at index nd
and the column offset is at index nd + 1
.
Correct Implementation:
x, y = i + dirs[nd], j + dirs[nd + 1]
4. Not Maintaining Relative Coordinate System
Starting with absolute coordinates or trying to determine the actual room layout.
Pitfall Example:
# Wrong: Trying to find actual starting position
def dfs(i, j, d):
# Attempting to map to actual room coordinates
actual_i = i + start_row # start_row is unknown!
actual_j = j + start_col # start_col is unknown!
Why It Fails: Since we don't know the robot's actual starting position in the room, we must use a relative coordinate system where the starting position is arbitrarily set as (0, 0).
Correct Approach: Always work with relative coordinates where the starting position is (0, 0), regardless of where the robot actually starts in the room.
Which data structure is used to implement priority queue?
Recommended Readings
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!