874. Walking Robot Simulation
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 degrees1
to9
: 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 5²
).
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:
-
Turn Left (
c = -2
):- Update direction:
k = (k + 3) % 4
- Adding 3 is equivalent to subtracting 1 in modulo 4, effectively rotating counterclockwise
- Update direction:
-
Turn Right (
c = -1
):- Update direction:
k = (k + 1) % 4
- This rotates clockwise through our direction cycle
- Update direction:
-
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 sets
- If blocked, break from the movement loop
- If clear, update position:
x, y = nx, ny
- Update maximum distance:
ans = max(ans, x² + y²)
- Calculate next position:
- For each of the
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 EvaluatorExample 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
- Step 1: Next position
- 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.
- Step 1: Next position
- 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, wherem
is the number of obstacles - Processing commands takes
O(C × n)
time, where:n
is the number of commands in thecommands
arrayC
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 takesO(1)
time - For each forward movement command with value
c
, the robot attempts to move up toc
steps, checking for obstacles at each step, which takesO(c)
time in the worst case - Since we could have up to
C
steps for any command, and we processn
commands total, this gives usO(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 allm
obstacles as tuples, requiringO(m)
space - Other variables (
dirs
,ans
,k
,x
,y
,nx
,ny
) use constant spaceO(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
What data structure does Breadth-first search typically uses to store intermediate states?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!