← Back to DSA
#2069

Walking Robot Simulation II

Medium
ArrayMathDesignSimulation
Solve on LeetCode ↗

Walking Robot Simulation II

If you are looking for a clean LeetCode 2069 Walking Robot Simulation II solution, this is the easiest way to think about it:

  • the robot never needs a full step-by-step simulation across the whole board
  • it only moves along the outer perimeter
  • that perimeter movement is a fixed cycle

So instead of simulating every query from scratch, we preprocess the full perimeter once, store the robot's position and direction for every state in that cycle, and answer step, getPos, and getDir in O(1).

This approach is perfect for the design requirement and works cleanly in Java, C++, JavaScript, TypeScript, C, C#, Go, and Rust.

Problem Summary

We need to design a Robot class for a grid of size width x height.

The robot:

  • starts at (0, 0)
  • starts facing "East"
  • moves one unit at a time
  • turns 90 degrees counterclockwise whenever the next move would leave the grid

We have to support three operations:

  1. step(num) updates the robot after num moves
  2. getPos() returns the current position
  3. getDir() returns the current direction

Key Insight

The robot always travels around the boundary of the rectangle.

It never enters the inner cells.

That means the robot's entire journey is just one repeating loop of perimeter cells:

  • move right on the bottom row
  • move up on the right column
  • move left on the top row
  • move down on the left column

So the real state space is not width * height. It is only the perimeter length:

2 * (width + height) - 4

Approach: Preprocess The Full Cycle

We build two arrays:

  • positions[i] = the robot position at cycle index i
  • directions[i] = the robot direction at cycle index i

We store the perimeter in traversal order:

  1. origin (0, 0) with direction "South"
  2. bottom row from left to right with direction "East"
  3. right column from bottom to top with direction "North"
  4. top row from right to left with direction "West"
  5. left column from top to bottom with direction "South"

Then we keep:

  • index = current location inside the cycle
  • moved = whether the robot has ever actually moved

For step(num):

  • mark moved = true
  • advance index by num % cycleLength

For getPos():

  • return positions[index]

For getDir():

  • if the robot never moved, return "East"
  • otherwise return directions[index]

Important Edge Case: The Origin Direction

This is the only tricky part in the whole problem.

At the very beginning:

  • position = (0, 0)
  • direction = "East"

But after the robot completes a full loop and comes back to (0, 0), its direction is:

  • "South"

So the same position has two possible directions depending on whether the robot has moved yet.

That is why we:

  • store the origin in the cycle with direction "South"
  • use moved to return "East" only for the untouched initial state

Algorithm

  1. Compute the perimeter cycle length 2 * (width + height) - 4
  2. Preprocess every perimeter state into positions and directions
  3. Store the current cycle index
  4. On step(num), move index = (index + num % cycleLength) % cycleLength
  5. On getPos(), return the stored position
  6. On getDir(), return "East" before any movement, otherwise the stored direction

Code Solution

Switch between languages

class Robot {
    private final int[][] positions;
    private final String[] directions;
    private final int cycleLength;
    private int index;
    private boolean moved;

    public Robot(int width, int height) {
        cycleLength = 2 * (width + height) - 4;
        positions = new int[cycleLength][2];
        directions = new String[cycleLength];

        int current = 0;
        positions[current][0] = 0;
        positions[current][1] = 0;
        directions[current++] = "South";

        for (int x = 1; x < width; x++) {
            positions[current][0] = x;
            positions[current][1] = 0;
            directions[current++] = "East";
        }

        for (int y = 1; y < height; y++) {
            positions[current][0] = width - 1;
            positions[current][1] = y;
            directions[current++] = "North";
        }

        for (int x = width - 2; x >= 0; x--) {
            positions[current][0] = x;
            positions[current][1] = height - 1;
            directions[current++] = "West";
        }

        for (int y = height - 2; y >= 1; y--) {
            positions[current][0] = 0;
            positions[current][1] = y;
            directions[current++] = "South";
        }
    }

    public void step(int num) {
        if (num == 0) {
            return;
        }

        moved = true;
        index = (index + num % cycleLength) % cycleLength;
    }

    public int[] getPos() {
        return new int[] {positions[index][0], positions[index][1]};
    }

    public String getDir() {
        return moved ? directions[index] : "East";
    }
}

Dry Run

Let width = 6 and height = 3.

The perimeter cycle is:

(0,0) S
(1,0) E
(2,0) E
(3,0) E
(4,0) E
(5,0) E
(5,1) N
(5,2) N
(4,2) W
(3,2) W
(2,2) W
(1,2) W
(0,2) W
(0,1) S

Initially:

  • index = 0
  • getPos() = [0, 0]
  • getDir() = "East" because moved = false

Now call step(2):

  • new index = 2
  • position becomes (2, 0)
  • direction becomes "East"

Now call step(12):

  • cycle length is 14
  • index = (2 + 12) % 14 = 0
  • position becomes (0, 0)
  • direction becomes "South" because the robot has completed a loop

That matches the problem's special origin behavior.

Why This Works

The robot's future behavior depends only on its current perimeter state.

Because the path repeats forever:

  • every move sequence is just movement on a cycle
  • every state has one fixed position
  • every state also has one fixed direction

So advancing by num steps is exactly the same as moving forward by num % cycleLength positions in that cycle.

The only exception is the untouched starting state, and that is handled by the moved flag.

Complexity Analysis

Time Complexity

  • preprocessing: O(width + height)
  • step(num): O(1)
  • getPos(): O(1)
  • getDir(): O(1)

Space Complexity

  • O(width + height)

We only store the perimeter states, not the whole grid.

Common Mistakes

1. Simulating every single step inside step(num)

That works, but it is unnecessary.

The whole point of this problem is to notice that the robot repeats the same perimeter cycle forever.

2. Forgetting the origin edge case

If you store (0, 0) with direction "East" for the cycle, then a full loop will return the wrong answer.

The cycle-state direction at origin must be "South".

3. Using the entire grid instead of only the perimeter

The robot never visits inner cells, so storing width * height states is wasteful and misses the main observation.

4. Forgetting modulo reduction

step(num) can be large, but only num % cycleLength matters.

Interview Takeaway

This is a classic example of turning a simulation problem into a cycle preprocessing problem.

When you see:

  • repeated movement
  • deterministic turning
  • and a path that eventually loops

it is often better to preprocess the loop once and answer future operations in constant time.

For Walking Robot Simulation II, that single insight makes the design simple, fast, and interview-friendly.

SEO Quick Summary

For LeetCode Walking Robot Simulation II, the optimal design idea is:

  • preprocess the robot's perimeter cycle
  • store position and direction for each cycle state
  • move with modulo arithmetic
  • handle the starting origin direction separately

That gives O(width + height) preprocessing and O(1) per query, which is exactly what you want for this robot simulation design problem.