Facebook Pixel

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. Returns true if successful (cell is empty), false if blocked by a wall
  • turnLeft(): Rotates the robot 90 degrees counter-clockwise without moving
  • turnRight(): Rotates the robot 90 degrees clockwise without moving
  • clean(): 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:

  1. Marks the current cell as visited and cleans it
  2. Tries to explore all 4 directions (up, right, down, left) by rotating
  3. For each direction, if the cell hasn't been visited and the robot can move there, recursively explores that path
  4. After exploring a direction, backtracks to the original position and orientation by turning 180 degrees, moving back, then turning 180 degrees again
  5. 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:
    1. Try moving in each direction from the current cell
    2. Recursively explore new cells when we can move there
    3. Backtrack to the previous position after exploring a path
    4. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Clean where you're standing
  2. Try to move forward
  3. If you can move, explore that new area completely
  4. When done exploring, come back to where you were
  5. 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 coordinates
  • dirs: 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.

  1. Mark and Clean: Add current position to vis and clean the cell

    vis.add((i, j))
    robot.clean()
  2. Explore All 4 Directions: Loop through 4 possible moves

    for k in range(4):
        nd = (d + k) % 4
    • k = 0: Try current direction
    • k = 1: Try after one right turn
    • k = 2: Try after two right turns
    • k = 3: Try after three right turns
  3. Calculate Next Position:

    x, y = i + dirs[nd], j + dirs[nd + 1]

    Using the direction index nd to get the appropriate offset from dirs.

  4. 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.

  5. 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
  6. 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 Evaluator

Example 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. Try move() → returns true
    • Move to (-1, 0)

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() → returns false (wall)
    • Turn right
    • Direction 1 (Right): Try (-1, 1). Not visited. move() → returns true
    • Move to (-1, 1)

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() → returns true
  • 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() → returns true
  • 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:

  1. We systematically try all 4 directions from each position by rotating
  2. We use relative coordinates since we don't know our absolute position
  3. After exploring each branch, we carefully backtrack to maintain our position and orientation
  4. The visited set prevents us from cleaning the same cell twice
  5. 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, requiring O(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 use O(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:

  1. Turn 180° to face the opposite direction
  2. Move back one cell
  3. 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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement priority queue?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More