Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions problems/2069-walking-robot-simulation-ii/analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# 2069. Walking Robot Simulation II

[LeetCode Link](https://leetcode.com/problems/walking-robot-simulation-ii/)

Difficulty: Medium
Topics: Design, Simulation
Acceptance Rate: 30.4%

## Hints

### Hint 1

The robot only ever walks along the perimeter of the grid. Think about what shape the robot's path traces and how you can represent its position more compactly than (x, y, direction).

### Hint 2

The perimeter of a W x H grid has exactly `2*(W-1) + 2*(H-1)` cells. You can map the robot's position to a single integer index along this perimeter and use modular arithmetic to avoid simulating each individual step.

### Hint 3

The tricky edge case is at position (0, 0). Initially the robot faces East, but if it returns to (0, 0) after walking, it arrived via the left edge traveling South. You need to track whether the robot has ever moved to distinguish these two cases.

## Approach

Model the robot's position as a single index along the perimeter of the grid. The perimeter has `P = 2*(W-1) + 2*(H-1)` positions, and the robot walks in a cycle:

1. **Bottom edge (East):** index 0 to W-1, cells (i, 0)
2. **Right edge (North):** index W to W+H-2, cells (W-1, i-W+1)
3. **Top edge (West):** index W+H-1 to 2W+H-3, cells (2W+H-3-i+W+H-1... simplified: W-2-offset, H-1)
4. **Left edge (South):** index 2W+H-2 to P-1, cells (0, H-2-offset)

When `step(num)` is called, simply do `pos = (pos + num) % P`. This is O(1) per call instead of O(num).

For `getPos()`, convert the perimeter index back to (x, y) coordinates by determining which edge the index falls on.

For `getDir()`, the direction is determined by which edge the index belongs to:
- Bottom edge -> "East"
- Right edge -> "North"
- Top edge -> "West"
- Left edge -> "South"

**Special case:** Position 0 is (0, 0). Initially the robot faces "East", but after any movement that lands back on index 0, the robot arrived from the South direction. Track a `moved` boolean to handle this.

## Complexity Analysis

Time Complexity: O(1) per `step`, `getPos`, and `getDir` call.
Space Complexity: O(1) - only stores width, height, position index, and a boolean flag.

## Edge Cases

- **Full perimeter walk:** After exactly P steps, the robot returns to (0, 0) but faces South, not East.
- **Large step counts:** `num` can be up to 10^5 with up to 10^4 calls, so total steps can reach 10^9. Modular arithmetic is essential; naive simulation would TLE.
- **Minimum grid (2x2):** Perimeter is only 4 cells. Each edge has exactly 1 step. Ensure the formulas work at these boundaries.
- **Multiple full loops:** `num` much larger than P should reduce correctly via modulo.
- **Corner positions:** Corners belong to the edge the robot was traveling along when it arrived (e.g., (W-1, 0) is East, not North).
76 changes: 76 additions & 0 deletions problems/2069-walking-robot-simulation-ii/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
number: "2069"
frontend_id: "2069"
title: "Walking Robot Simulation II"
slug: "walking-robot-simulation-ii"
difficulty: "Medium"
topics:
- "Design"
- "Simulation"
acceptance_rate: 3038.2
is_premium: false
created_at: "2026-04-07T03:36:34.001142+00:00"
fetched_at: "2026-04-07T03:36:34.001142+00:00"
link: "https://leetcode.com/problems/walking-robot-simulation-ii/"
date: "2026-04-07"
---

# 2069. Walking Robot Simulation II

A `width x height` grid is on an XY-plane with the **bottom-left** cell at `(0, 0)` and the **top-right** cell at `(width - 1, height - 1)`. The grid is aligned with the four cardinal directions (`"North"`, `"East"`, `"South"`, and `"West"`). A robot is **initially** at cell `(0, 0)` facing direction `"East"`.

The robot can be instructed to move for a specific number of **steps**. For each step, it does the following.

1. Attempts to move **forward one** cell in the direction it is facing.
2. If the cell the robot is **moving to** is **out of bounds** , the robot instead **turns** 90 degrees **counterclockwise** and retries the step.



After the robot finishes moving the number of steps required, it stops and awaits the next instruction.

Implement the `Robot` class:

* `Robot(int width, int height)` Initializes the `width x height` grid with the robot at `(0, 0)` facing `"East"`.
* `void step(int num)` Instructs the robot to move forward `num` steps.
* `int[] getPos()` Returns the current cell the robot is at, as an array of length 2, `[x, y]`.
* `String getDir()` Returns the current direction of the robot, `"North"`, `"East"`, `"South"`, or `"West"`.





**Example 1:**

![example-1](https://assets.leetcode.com/uploads/2021/10/09/example-1.png)


**Input**
["Robot", "step", "step", "getPos", "getDir", "step", "step", "step", "getPos", "getDir"]
[[6, 3], [2], [2], [], [], [2], [1], [4], [], []]
**Output**
[null, null, null, [4, 0], "East", null, null, null, [1, 2], "West"]

**Explanation**
Robot robot = new Robot(6, 3); // Initialize the grid and the robot at (0, 0) facing East.
robot.step(2); // It moves two steps East to (2, 0), and faces East.
robot.step(2); // It moves two steps East to (4, 0), and faces East.
robot.getPos(); // return [4, 0]
robot.getDir(); // return "East"
robot.step(2); // It moves one step East to (5, 0), and faces East.
// Moving the next step East would be out of bounds, so it turns and faces North.
// Then, it moves one step North to (5, 1), and faces North.
robot.step(1); // It moves one step North to (5, 2), and faces **North** (not West).
robot.step(4); // Moving the next step North would be out of bounds, so it turns and faces West.
// Then, it moves four steps West to (1, 2), and faces West.
robot.getPos(); // return [1, 2]
robot.getDir(); // return "West"





**Constraints:**

* `2 <= width, height <= 100`
* `1 <= num <= 105`
* At most `104` calls **in total** will be made to `step`, `getPos`, and `getDir`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

// Walking Robot Simulation II
// Model the robot's position as an index along the grid perimeter.
// Use modular arithmetic in Step() for O(1) per call.
// Convert perimeter index to (x, y) and direction on demand.

type Robot struct {
w, h int
pos int
moved bool
}

func Constructor(width int, height int) Robot {
return Robot{w: width, h: height}
}

func (r *Robot) Step(num int) {
p := 2*(r.w-1) + 2*(r.h-1)
r.pos = (r.pos + num) % p
r.moved = true
}

func (r *Robot) GetPos() []int {
x, y := r.getXY()
return []int{x, y}
}

func (r *Robot) GetDir() string {
pos := r.pos
w, h := r.w, r.h
if pos == 0 {
if r.moved {
return "South"
}
return "East"
}
if pos < w {
return "East"
}
if pos < w+h-1 {
return "North"
}
if pos < 2*w+h-2 {
return "West"
}
return "South"
}

func (r *Robot) getXY() (int, int) {
pos := r.pos
w, h := r.w, r.h
if pos < w {
return pos, 0
}
pos -= w
if pos < h-1 {
return w - 1, pos + 1
}
pos -= h - 1
if pos < w-1 {
return w - 2 - pos, h - 1
}
pos -= w - 1
return 0, h - 2 - pos
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package main

import "testing"

func TestRobot(t *testing.T) {
tests := []struct {
name string
width int
height int
operations []string
stepArgs []int
wantPos [][]int // nil entries for non-getPos operations
wantDir []*string // nil entries for non-getDir operations
}{
{
name: "example 1: 6x3 grid with mixed operations",
width: 6,
height: 3,
operations: []string{"step", "step", "getPos", "getDir", "step", "step", "step", "getPos", "getDir"},
stepArgs: []int{2, 2, 0, 0, 2, 1, 4, 0, 0},
wantPos: [][]int{nil, nil, {4, 0}, nil, nil, nil, nil, {1, 2}, nil},
wantDir: []*string{nil, nil, nil, strPtr("East"), nil, nil, nil, nil, strPtr("West")},
},
{
name: "edge case: no steps, initial position",
width: 3,
height: 3,
operations: []string{"getPos", "getDir"},
stepArgs: []int{0, 0},
wantPos: [][]int{{0, 0}, nil},
wantDir: []*string{nil, strPtr("East")},
},
{
name: "edge case: full perimeter returns to origin facing South",
width: 4,
height: 3,
operations: []string{"step", "getPos", "getDir"},
stepArgs: []int{10, 0, 0}, // P = 2*3 + 2*2 = 10
wantPos: [][]int{nil, {0, 0}, nil},
wantDir: []*string{nil, nil, strPtr("South")},
},
{
name: "edge case: minimum 2x2 grid",
width: 2,
height: 2,
operations: []string{"step", "getPos", "getDir", "step", "getPos", "getDir", "step", "getPos", "getDir", "step", "getPos", "getDir"},
stepArgs: []int{1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0},
wantPos: [][]int{nil, {1, 0}, nil, nil, {1, 1}, nil, nil, {0, 1}, nil, nil, {0, 0}, nil},
wantDir: []*string{nil, nil, strPtr("East"), nil, nil, strPtr("North"), nil, nil, strPtr("West"), nil, nil, strPtr("South")},
},
{
name: "edge case: large steps with modulo",
width: 6,
height: 3,
operations: []string{"step", "getPos", "getDir"},
stepArgs: []int{100000, 0, 0},
// P = 2*5 + 2*2 = 14, 100000 % 14 = 12 -> (0, 2), West (top-left corner)
wantPos: [][]int{nil, {0, 2}, nil},
wantDir: []*string{nil, nil, strPtr("West")},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
robot := Constructor(tt.width, tt.height)
for i, op := range tt.operations {
switch op {
case "step":
robot.Step(tt.stepArgs[i])
case "getPos":
got := robot.GetPos()
want := tt.wantPos[i]
if want != nil && (got[0] != want[0] || got[1] != want[1]) {
t.Errorf("operation %d getPos() = %v, want %v", i, got, want)
}
case "getDir":
got := robot.GetDir()
want := tt.wantDir[i]
if want != nil && got != *want {
t.Errorf("operation %d getDir() = %q, want %q", i, got, *want)
}
}
}
})
}
}

func TestRobotCornerDirections(t *testing.T) {
// Verify direction at each corner of a 3x3 grid (P=8)
robot := Constructor(3, 3)

// Walk to each corner and check direction
corners := []struct {
steps int
wantPos []int
wantDir string
}{
{2, []int{2, 0}, "East"}, // bottom-right corner
{2, []int{2, 2}, "North"}, // top-right corner
{2, []int{0, 2}, "West"}, // top-left corner
{2, []int{0, 0}, "South"}, // back to origin
}

for i, c := range corners {
robot.Step(c.steps)
pos := robot.GetPos()
dir := robot.GetDir()
if pos[0] != c.wantPos[0] || pos[1] != c.wantPos[1] {
t.Errorf("corner %d: getPos() = %v, want %v", i, pos, c.wantPos)
}
if dir != c.wantDir {
t.Errorf("corner %d: getDir() = %q, want %q", i, dir, c.wantDir)
}
}
}

func TestRobotMultipleLoops(t *testing.T) {
// 5x4 grid, P = 2*4 + 2*3 = 14
robot := Constructor(5, 4)
// 3 full loops + 5 extra steps
robot.Step(3*14 + 5)
pos := robot.GetPos()
dir := robot.GetDir()
// pos 5 is on right edge: (4, 1), North
if pos[0] != 4 || pos[1] != 1 {
t.Errorf("getPos() = %v, want [4 1]", pos)
}
if dir != "North" {
t.Errorf("getDir() = %q, want %q", dir, "North")
}
}

func strPtr(s string) *string {
return &s
}