Facebook Pixel

874. Walking Robot Simulation

MediumArrayHash TableSimulation
Leetcode Link

Problem Description

You have a robot that starts at position (0, 0) on an infinite XY-plane, initially facing north. The robot needs to execute a sequence of commands given as an array of integers.

The robot can receive three types of commands:

  • -2: Turn left 90 degrees
  • -1: Turn right 90 degrees
  • 1 to 9: Move forward by that many units, one step at a time

The plane contains obstacles at certain positions given as an array obstacles, where each obstacles[i] = (xi, yi) represents an obstacle at that coordinate. When the robot tries to move into an obstacle, it stops at the position just before the obstacle and continues with the next command.

Your task is to find the maximum squared Euclidean distance from the origin that the robot reaches at any point during its journey. The squared Euclidean distance from origin (0, 0) to point (x, y) is calculated as x² + y².

Important details:

  • The robot moves in the four cardinal directions: North (+Y), East (+X), South (-Y), and West (-X)
  • If there's an obstacle at (0, 0), the robot ignores it initially but cannot return to the origin later
  • When executing a forward movement command of k units, the robot moves one unit at a time and checks for obstacles at each step
  • If an obstacle blocks the path, the robot stops and proceeds to the next command

For example, if the robot reaches a maximum distance of 5 units from the origin at some point, you should return 25 (which is ).

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

Intuition

The problem is essentially a simulation where we need to track the robot's position as it follows commands. The key insight is that we need to handle three distinct operations: turning left, turning right, and moving forward.

For handling directions, instead of using complex conditional logic or switch statements, we can use a clever trick with a direction array. If we represent the four cardinal directions as vectors: North (0, 1), East (1, 0), South (0, -1), and West (-1, 0), we notice these form a cycle. We can store these as a flattened array [0, 1, 0, -1, 0] where consecutive pairs represent each direction.

Why this representation? When we turn right, we move to the next direction in the cycle (incrementing our direction index by 1). When we turn left, we move three positions forward in the cycle (which is equivalent to moving one position backward). Using modulo 4 ensures we wrap around correctly.

For obstacle detection, we need to check if a position is blocked before moving there. Since we might need to check many positions (the robot can move up to 9 steps per command), and there could be many obstacles, using a hash set for obstacle coordinates gives us O(1) lookup time for each position check.

The crucial observation for forward movement is that we must move one unit at a time rather than jumping directly to the final position. This is because an obstacle might block our path midway, and we need to stop just before hitting it. Each step forward involves calculating the next position based on our current direction and checking if it's blocked.

Throughout the simulation, we track the maximum squared distance after each successful move. We calculate x² + y² at each new position and update our answer if this value is larger than what we've seen before. This ensures we capture the farthest point the robot reaches during its entire journey.

Solution Approach

The implementation follows a simulation approach with efficient data structures for direction management and obstacle detection.

Direction Management: We use a direction array dirs = [0, 1, 0, -1, 0] where consecutive pairs represent direction vectors:

  • Index k=0: (dirs[0], dirs[1]) = (0, 1) represents North
  • Index k=1: (dirs[1], dirs[2]) = (1, 0) represents East
  • Index k=2: (dirs[2], dirs[3]) = (0, -1) represents South
  • Index k=3: (dirs[3], dirs[4]) = (-1, 0) represents West

The variable k tracks our current direction index (0 to 3).

Obstacle Storage: We convert the obstacles list into a set s = {(x, y) for x, y in obstacles} for O(1) lookup time when checking if a position is blocked.

Command Processing: We iterate through each command c in the commands array:

  1. Turn Left (c = -2):

    • Update direction: k = (k + 3) % 4
    • Adding 3 is equivalent to subtracting 1 in modulo 4, effectively rotating counterclockwise
  2. Turn Right (c = -1):

    • Update direction: k = (k + 1) % 4
    • This rotates clockwise through our direction cycle
  3. Move Forward (1 ≤ c ≤ 9):

    • For each of the c steps:
      • Calculate next position: nx = x + dirs[k], ny = y + dirs[k + 1]
      • Check if (nx, ny) is in obstacle set s
      • If blocked, break from the movement loop
      • If clear, update position: x, y = nx, ny
      • Update maximum distance: ans = max(ans, x² + y²)

Tracking Maximum Distance: After each successful move, we calculate the squared Euclidean distance x² + y² and update ans if this exceeds the previous maximum. This ensures we capture the farthest point reached throughout the entire journey.

The algorithm runs in O(M + N) time complexity, where M is the total number of steps taken (sum of all forward movement commands) and N is the number of obstacles (for set creation). The space complexity is O(N) for storing the obstacle set.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example to illustrate the solution approach.

Input:

  • commands = [4, -1, 3]
  • obstacles = [(2, 4)]

Initial Setup:

  • Robot starts at position (0, 0) facing North
  • Direction array: dirs = [0, 1, 0, -1, 0]
  • Current direction index: k = 0 (North)
  • Obstacle set: s = {(2, 4)}
  • Maximum distance: ans = 0

Step 1: Command 4 (move forward 4 units)

  • Robot is facing North, so direction vector is (dirs[0], dirs[1]) = (0, 1)
  • Move one unit at a time:
    • Step 1: Next position (0+0, 0+1) = (0, 1). Not blocked. Move to (0, 1). Distance = 0² + 1² = 1
    • Step 2: Next position (0+0, 1+1) = (0, 2). Not blocked. Move to (0, 2). Distance = 0² + 2² = 4
    • Step 3: Next position (0+0, 2+1) = (0, 3). Not blocked. Move to (0, 3). Distance = 0² + 3² = 9
    • Step 4: Next position (0+0, 3+1) = (0, 4). Not blocked. Move to (0, 4). Distance = 0² + 4² = 16
  • Current position: (0, 4), ans = 16

Step 2: Command -1 (turn right)

  • Update direction: k = (0 + 1) % 4 = 1
  • Robot now faces East (dirs[1], dirs[2]) = (1, 0)
  • Position remains (0, 4), ans = 16

Step 3: Command 3 (move forward 3 units)

  • Robot is facing East, so direction vector is (1, 0)
  • Move one unit at a time:
    • Step 1: Next position (0+1, 4+0) = (1, 4). Not blocked. Move to (1, 4). Distance = 1² + 4² = 17
    • Step 2: Next position (1+1, 4+0) = (2, 4). Blocked by obstacle! Stop here.
  • Robot stops at (1, 4) and cannot complete the remaining movement
  • Current position: (1, 4), ans = 17

Final Result: The maximum squared distance reached is 17 (at position (1, 4)).

This example demonstrates:

  • How the robot moves one unit at a time
  • How obstacles block movement mid-command
  • How turning changes the direction index
  • How we track the maximum distance throughout the journey

Solution Implementation

1class Solution:
2    def robotSim(self, commands: List[int], obstacles: List[List[int]]) -> int:
3        # Direction vectors: North(0,1), East(1,0), South(0,-1), West(-1,0)
4        # Stored as (dx1, dy1, dx2, dy2, dx3, dy3, dx4, dy4) in flattened array
5        directions = (0, 1, 0, -1, 0)
6      
7        # Convert obstacles list to set for O(1) lookup
8        obstacle_set = {(x, y) for x, y in obstacles}
9      
10        # Initialize maximum distance and current direction index
11        max_distance_squared = 0
12        direction_index = 0  # 0: North, 1: East, 2: South, 3: West
13      
14        # Starting position
15        current_x = 0
16        current_y = 0
17      
18        # Process each command
19        for command in commands:
20            if command == -2:
21                # Turn left: counterclockwise rotation (equivalent to +3 mod 4)
22                direction_index = (direction_index + 3) % 4
23            elif command == -1:
24                # Turn right: clockwise rotation
25                direction_index = (direction_index + 1) % 4
26            else:
27                # Move forward 'command' steps
28                for _ in range(command):
29                    # Calculate next position based on current direction
30                    next_x = current_x + directions[direction_index]
31                    next_y = current_y + directions[direction_index + 1]
32                  
33                    # Check if next position is an obstacle
34                    if (next_x, next_y) in obstacle_set:
35                        break
36                  
37                    # Update current position
38                    current_x = next_x
39                    current_y = next_y
40                  
41                    # Update maximum Euclidean distance squared from origin
42                    max_distance_squared = max(max_distance_squared, 
43                                              current_x * current_x + current_y * current_y)
44      
45        return max_distance_squared
46
1class Solution {
2    public int robotSim(int[] commands, int[][] obstacles) {
3        // Direction vectors: North(0,1), East(1,0), South(0,-1), West(-1,0)
4        // Stored as [dx0, dy0, dx1, dy1, dx2, dy2, dx3, dy3] in a flattened array
5        int[] directions = {0, 1, 0, -1, 0};
6      
7        // Create a set to store obstacle positions for O(1) lookup
8        Set<Integer> obstacleSet = new HashSet<>(obstacles.length);
9        for (int[] obstacle : obstacles) {
10            // Encode the 2D coordinate into a single integer for efficient storage
11            obstacleSet.add(encodePosition(obstacle[0], obstacle[1]));
12        }
13      
14        // Initialize variables
15        int maxDistanceSquared = 0;  // Track maximum Euclidean distance squared from origin
16        int directionIndex = 0;       // Current direction index (0=North, 1=East, 2=South, 3=West)
17        int currentX = 0;             // Current x position
18        int currentY = 0;             // Current y position
19      
20        // Process each command
21        for (int command : commands) {
22            if (command == -2) {
23                // Turn left: decrease direction index by 1 (equivalent to +3 mod 4)
24                directionIndex = (directionIndex + 3) % 4;
25            } else if (command == -1) {
26                // Turn right: increase direction index by 1
27                directionIndex = (directionIndex + 1) % 4;
28            } else {
29                // Move forward 'command' steps
30                while (command-- > 0) {
31                    // Calculate next position
32                    int nextX = currentX + directions[directionIndex];
33                    int nextY = currentY + directions[directionIndex + 1];
34                  
35                    // Check if next position contains an obstacle
36                    if (obstacleSet.contains(encodePosition(nextX, nextY))) {
37                        break;  // Stop moving if obstacle is encountered
38                    }
39                  
40                    // Update current position
41                    currentX = nextX;
42                    currentY = nextY;
43                  
44                    // Update maximum distance squared from origin
45                    maxDistanceSquared = Math.max(maxDistanceSquared, 
46                                                  currentX * currentX + currentY * currentY);
47                }
48            }
49        }
50      
51        return maxDistanceSquared;
52    }
53  
54    /**
55     * Encodes a 2D coordinate (x, y) into a single integer.
56     * Uses a large multiplier to avoid collisions between different coordinates.
57     * The multiplier 60010 is chosen to be larger than the possible range of coordinates.
58     * 
59     * @param x the x-coordinate
60     * @param y the y-coordinate
61     * @return the encoded position as a single integer
62     */
63    private int encodePosition(int x, int y) {
64        return x * 60010 + y;
65    }
66}
67
1class Solution {
2public:
3    int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
4        // Direction vectors: North(0,1), East(1,0), South(0,-1), West(-1,0)
5        // Using array to store dx and dy in sequence for easy rotation
6        int directions[5] = {0, 1, 0, -1, 0};
7      
8        // Hash function to convert 2D coordinates to unique integer
9        // Multiplier 60010 ensures no collision for valid coordinate range
10        auto hashCoordinate = [](int x, int y) {
11            return x * 60010 + y;
12        };
13      
14        // Store obstacle positions in hash set for O(1) lookup
15        unordered_set<int> obstacleSet;
16        for (auto& obstacle : obstacles) {
17            obstacleSet.insert(hashCoordinate(obstacle[0], obstacle[1]));
18        }
19      
20        // Initialize robot state
21        int maxDistanceSquared = 0;  // Maximum Euclidean distance squared from origin
22        int directionIndex = 0;       // Current direction index (0=North, 1=East, 2=South, 3=West)
23        int currentX = 0, currentY = 0;  // Current robot position
24      
25        // Process each command
26        for (int command : commands) {
27            if (command == -2) {
28                // Turn left: rotate counter-clockwise by 90 degrees
29                directionIndex = (directionIndex + 3) % 4;
30            } else if (command == -1) {
31                // Turn right: rotate clockwise by 90 degrees
32                directionIndex = (directionIndex + 1) % 4;
33            } else {
34                // Move forward by 'command' steps
35                while (command--) {
36                    // Calculate next position
37                    int nextX = currentX + directions[directionIndex];
38                    int nextY = currentY + directions[directionIndex + 1];
39                  
40                    // Check if next position has an obstacle
41                    if (obstacleSet.count(hashCoordinate(nextX, nextY))) {
42                        break;  // Stop moving if obstacle encountered
43                    }
44                  
45                    // Update position
46                    currentX = nextX;
47                    currentY = nextY;
48                  
49                    // Update maximum distance squared
50                    maxDistanceSquared = max(maxDistanceSquared, currentX * currentX + currentY * currentY);
51                }
52            }
53        }
54      
55        return maxDistanceSquared;
56    }
57};
58
1/**
2 * Simulates a robot walking on a 2D plane with obstacles
3 * @param commands - Array of commands: -2 (turn left), -1 (turn right), or 1-9 (move forward)
4 * @param obstacles - Array of obstacle coordinates [x, y]
5 * @returns Maximum Euclidean distance squared from origin during the walk
6 */
7function robotSim(commands: number[], obstacles: number[][]): number {
8    // Direction vectors for North, East, South, West (in order)
9    // Using pairs: (dx[0], dy[0]) = North, (dx[1], dy[1]) = East, etc.
10    const directionDeltas: number[] = [0, 1, 0, -1, 0];
11  
12    // Set to store hashed obstacle positions for O(1) lookup
13    const obstacleSet: Set<number> = new Set<number>();
14  
15    /**
16     * Hash function to convert 2D coordinates to unique number
17     * Using 60010 as multiplier to avoid collisions (larger than coordinate range)
18     */
19    const hashCoordinate = (x: number, y: number): number => {
20        return x * 60010 + y;
21    };
22  
23    // Add all obstacles to the set using hash function
24    for (const [obstacleX, obstacleY] of obstacles) {
25        obstacleSet.add(hashCoordinate(obstacleX, obstacleY));
26    }
27  
28    // Initialize robot state
29    let maxDistanceSquared: number = 0;  // Maximum distance squared from origin
30    let currentX: number = 0;             // Current x position
31    let currentY: number = 0;             // Current y position
32    let directionIndex: number = 0;       // Current direction (0=North, 1=East, 2=South, 3=West)
33  
34    // Process each command
35    for (const command of commands) {
36        if (command === -2) {
37            // Turn left: rotate 90 degrees counter-clockwise
38            directionIndex = (directionIndex + 3) % 4;
39        } else if (command === -1) {
40            // Turn right: rotate 90 degrees clockwise
41            directionIndex = (directionIndex + 1) % 4;
42        } else {
43            // Move forward 'command' steps (1-9)
44            let stepsRemaining: number = command;
45          
46            while (stepsRemaining > 0) {
47                // Calculate next position
48                const nextX: number = currentX + directionDeltas[directionIndex];
49                const nextY: number = currentY + directionDeltas[directionIndex + 1];
50              
51                // Check if next position has an obstacle
52                if (obstacleSet.has(hashCoordinate(nextX, nextY))) {
53                    break;  // Stop moving if obstacle encountered
54                }
55              
56                // Update position
57                currentX = nextX;
58                currentY = nextY;
59              
60                // Update maximum distance squared
61                maxDistanceSquared = Math.max(maxDistanceSquared, currentX * currentX + currentY * currentY);
62              
63                stepsRemaining--;
64            }
65        }
66    }
67  
68    return maxDistanceSquared;
69}
70

Time and Space Complexity

Time Complexity: O(C × n + m)

The time complexity consists of two main parts:

  • Creating the obstacle set takes O(m) time, where m is the number of obstacles
  • Processing commands takes O(C × n) time, where:
    • n is the number of commands in the commands array
    • C is the maximum distance value in a forward movement command (the maximum value of positive commands)
    • For each command, if it's a turn command (-1 or -2), it takes O(1) time
    • For each forward movement command with value c, the robot attempts to move up to c steps, checking for obstacles at each step, which takes O(c) time in the worst case
    • Since we could have up to C steps for any command, and we process n commands total, this gives us O(C × n)

Overall time complexity: O(C × n + m)

Space Complexity: O(m)

The space complexity is determined by:

  • The obstacle set s which stores all m obstacles as tuples, requiring O(m) space
  • Other variables (dirs, ans, k, x, y, nx, ny) use constant space O(1)

Overall space complexity: O(m)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Update Maximum Distance After Each Step

Pitfall: A common mistake is only checking the maximum distance after completing all commands or after each command, rather than after each individual step during forward movement.

# INCORRECT: Only updates after completing the entire movement command
for command in commands:
    if command > 0:
        for _ in range(command):
            next_x = current_x + directions[direction_index]
            next_y = current_y + directions[direction_index + 1]
            if (next_x, next_y) not in obstacle_set:
                current_x = next_x
                current_y = next_y
        # Wrong: Only checking distance after all steps
        max_distance_squared = max(max_distance_squared, 
                                  current_x * current_x + current_y * current_y)

Solution: Update the maximum distance after EACH successful step, not just after completing a command:

# CORRECT: Updates after each individual step
for _ in range(command):
    next_x = current_x + directions[direction_index]
    next_y = current_y + directions[direction_index + 1]
    if (next_x, next_y) in obstacle_set:
        break
    current_x = next_x
    current_y = next_y
    # Correct: Check distance after each step
    max_distance_squared = max(max_distance_squared, 
                              current_x * current_x + current_y * current_y)

2. Incorrect Handling of Obstacle Set Creation

Pitfall: Creating the obstacle set with incorrect tuple unpacking or data structure:

# INCORRECT: Creates set of lists (unhashable type error)
obstacle_set = {obstacle for obstacle in obstacles}

# INCORRECT: Creates set incorrectly if obstacles format varies
obstacle_set = set(obstacles)  # May fail if obstacles contains lists

Solution: Explicitly convert each obstacle to a tuple:

# CORRECT: Ensures each obstacle is a hashable tuple
obstacle_set = {(x, y) for x, y in obstacles}
# Or alternatively:
obstacle_set = {tuple(obstacle) for obstacle in obstacles}

3. Off-by-One Error in Direction Array Indexing

Pitfall: Using incorrect indexing when accessing direction vectors from the flattened array:

# INCORRECT: May cause index out of bounds
directions = [0, 1, 0, -1, 0]
next_x = current_x + directions[direction_index * 2]      # Wrong formula
next_y = current_y + directions[direction_index * 2 + 1]  # Wrong formula

Solution: Use the correct indexing pattern where consecutive elements form direction pairs:

# CORRECT: Proper indexing for flattened direction array
directions = (0, 1, 0, -1, 0)
next_x = current_x + directions[direction_index]      # Index k gives dx
next_y = current_y + directions[direction_index + 1]  # Index k+1 gives dy

4. Not Breaking Immediately When Obstacle is Encountered

Pitfall: Continuing to try moving after hitting an obstacle within the same command:

# INCORRECT: Doesn't stop when obstacle is hit
for _ in range(command):
    next_x = current_x + directions[direction_index]
    next_y = current_y + directions[direction_index + 1]
    if (next_x, next_y) not in obstacle_set:
        current_x = next_x
        current_y = next_y
    # Wrong: Continues looping even after hitting obstacle

Solution: Use break to stop the movement loop immediately upon encountering an obstacle:

# CORRECT: Stops immediately when obstacle is encountered
for _ in range(command):
    next_x = current_x + directions[direction_index]
    next_y = current_y + directions[direction_index + 1]
    if (next_x, next_y) in obstacle_set:
        break  # Stop trying to move further
    current_x = next_x
    current_y = next_y
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What data structure does Breadth-first search typically uses to store intermediate states?


Recommended Readings

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

Load More