LeetCode 3248 - Snake in Matrix
The problem describes a snake moving inside an n x n grid. Every cell in the grid is assigned a numeric position using the formula: This means the grid is numbered row by row, starting from the top-left corner.
Difficulty: 🟢 Easy
Topics: Array, String, Simulation
Solution
Problem Understanding
The problem describes a snake moving inside an n x n grid. Every cell in the grid is assigned a numeric position using the formula:
$$\text{grid}[i][j] = (i \times n) + j$$
This means the grid is numbered row by row, starting from the top-left corner.
For example, when n = 3, the grid looks like this:
| Row | Values |
|---|---|
| 0 | 0 1 2 |
| 1 | 3 4 5 |
| 2 | 6 7 8 |
The snake always starts at position 0, which corresponds to the top-left corner (0, 0).
You are given a sequence of movement commands. Each command tells the snake to move one cell in one of four directions:
"UP""DOWN""LEFT""RIGHT"
The task is to determine the numeric position of the snake after executing all commands.
The constraints are very small:
nis at most10- There are at most
100commands
This tells us the input size is tiny, so performance is not a concern. Even a straightforward simulation will easily fit within limits.
An important guarantee is that the snake never moves outside the grid boundaries. This means we do not need to write boundary-checking logic. Every move is always valid.
Some important edge cases include:
- A command sequence that moves back and forth repeatedly.
- Movement only in one direction.
- Smallest grid size,
n = 2. - Returning back to the starting cell after several moves.
- Multiple vertical moves, because vertical movement changes the position by
n, not by1.
A naive implementation can easily make mistakes when converting between row-column coordinates and the flattened cell index, especially for vertical movement.
Approaches
Brute Force Approach
The most direct solution is to explicitly track the snake's row and column coordinates.
We start with:
row = 0col = 0
For each command:
"UP"decreases the row."DOWN"increases the row."LEFT"decreases the column."RIGHT"increases the column.
After processing all commands, we convert the final (row, col) position back into the cell number using:
$$\text{position} = (row \times n) + col$$
This approach is correct because every command directly corresponds to a movement inside the matrix.
Although this solution is already efficient enough for the given constraints, it still performs unnecessary coordinate conversions.
Optimal Approach
The key observation is that we do not actually need to maintain row and column separately.
Because the grid numbering follows:
$$\text{grid}[i][j] = (i \times n) + j$$
we can directly update the numeric position itself.
Moving:
"RIGHT"increases position by1"LEFT"decreases position by1"DOWN"increases position byn"UP"decreases position byn
This works because horizontal movement changes the column index by one, while vertical movement changes the row index by one entire row of length n.
Instead of converting coordinates repeatedly, we directly simulate movement on the flattened index.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(m) | O(1) | Tracks row and column separately |
| Optimal | O(m) | O(1) | Directly updates flattened position index |
Here, m is the number of commands.
Algorithm Walkthrough
- Initialize a variable
position = 0.
The snake always starts at cell 0, so this variable represents the current location.
2. Iterate through every command in commands.
Each command represents one movement step. 3. For each command, update the position accordingly.
- If the command is
"RIGHT", add1. - If the command is
"LEFT", subtract1. - If the command is
"DOWN", addn. - If the command is
"UP", subtractn.
These updates directly match how cells are numbered in the matrix.
4. After processing all commands, return position.
This value already represents the final flattened cell index.
Why it works
The numbering system assigns consecutive numbers across rows. Horizontal movement changes the index by exactly 1, while vertical movement skips an entire row of length n. Since every command corresponds exactly to one of these transformations, repeatedly applying these updates always produces the correct final cell index.
Python Solution
from typing import List
class Solution:
def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:
position = 0
for command in commands:
if command == "RIGHT":
position += 1
elif command == "LEFT":
position -= 1
elif command == "DOWN":
position += n
else: # "UP"
position -= n
return position
The implementation begins by initializing position to 0, because the snake always starts at the top-left cell.
The loop processes each movement command one at a time. Instead of maintaining row and column coordinates separately, the code directly modifies the flattened index.
Horizontal movements adjust the index by 1, while vertical movements adjust it by n, which corresponds to moving an entire row up or down.
Finally, the method returns the computed position after all commands have been executed.
Go Solution
func finalPositionOfSnake(n int, commands []string) int {
position := 0
for _, command := range commands {
if command == "RIGHT" {
position += 1
} else if command == "LEFT" {
position -= 1
} else if command == "DOWN" {
position += n
} else { // "UP"
position -= n
}
}
return position
}
The Go implementation follows exactly the same logic as the Python version.
Go uses a for _, command := range commands loop to iterate through the slice of commands. String comparisons are performed directly using ==.
No special handling is required for empty slices because the constraints guarantee at least one command. Integer overflow is also not a concern because the grid size and number of commands are extremely small.
Worked Examples
Example 1
Input:
n = 2
commands = ["RIGHT", "DOWN"]
Grid:
| 0 | 1 |
| 2 | 3 |
Initial position:
position = 0
Process commands:
| Command | Calculation | New Position |
|---|---|---|
| RIGHT | 0 + 1 | 1 |
| DOWN | 1 + 2 | 3 |
Final answer:
3
Example 2
Input:
n = 3
commands = ["DOWN", "RIGHT", "UP"]
Grid:
| 0 | 1 | 2 |
| 3 | 4 | 5 |
| 6 | 7 | 8 |
Initial position:
position = 0
Process commands:
| Command | Calculation | New Position |
|---|---|---|
| DOWN | 0 + 3 | 3 |
| RIGHT | 3 + 1 | 4 |
| UP | 4 - 3 | 1 |
Final answer:
1
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m) | Each command is processed once |
| Space | O(1) | Only a single position variable is used |
The algorithm performs one constant-time update for every command. No additional data structures are allocated, so the memory usage remains constant regardless of input size.
Test Cases
from typing import List
class Solution:
def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:
position = 0
for command in commands:
if command == "RIGHT":
position += 1
elif command == "LEFT":
position -= 1
elif command == "DOWN":
position += n
else:
position -= n
return position
sol = Solution()
assert sol.finalPositionOfSnake(2, ["RIGHT", "DOWN"]) == 3 # provided example 1
assert sol.finalPositionOfSnake(3, ["DOWN", "RIGHT", "UP"]) == 1 # provided example 2
assert sol.finalPositionOfSnake(2, ["DOWN"]) == 2 # single vertical move
assert sol.finalPositionOfSnake(2, ["RIGHT"]) == 1 # single horizontal move
assert sol.finalPositionOfSnake(3, ["RIGHT", "LEFT"]) == 0 # returns to start
assert sol.finalPositionOfSnake(4, ["DOWN", "DOWN"]) == 8 # multiple row jumps
assert sol.finalPositionOfSnake(5, ["RIGHT", "RIGHT", "DOWN"]) == 7 # mixed movement
assert sol.finalPositionOfSnake(5, ["DOWN", "RIGHT", "RIGHT", "UP", "LEFT"]) == 1 # complex path
assert sol.finalPositionOfSnake(10, ["DOWN"] * 9) == 90 # maximum vertical traversal
assert sol.finalPositionOfSnake(10, ["RIGHT"] * 9) == 9 # maximum horizontal traversal
| Test | Why |
|---|---|
n=2, ["RIGHT","DOWN"] |
Validates provided example |
n=3, ["DOWN","RIGHT","UP"] |
Validates mixed movement |
n=2, ["DOWN"] |
Tests vertical movement |
n=2, ["RIGHT"] |
Tests horizontal movement |
["RIGHT","LEFT"] |
Ensures reverse movement works |
["DOWN","DOWN"] |
Tests repeated row transitions |
| Mixed movement path | Verifies combined operations |
| Return near start | Ensures correct incremental updates |
| Maximum downward moves | Tests large vertical offsets |
| Maximum rightward moves | Tests edge horizontal traversal |
Edge Cases
One important edge case is movement that immediately reverses direction, such as "RIGHT" followed by "LEFT". A buggy implementation might incorrectly accumulate movement or mishandle index updates. The solution handles this naturally because each command directly modifies the current position, and opposite operations cancel each other out correctly.
Another important case is repeated vertical movement. Since vertical movement changes the flattened index by n rather than 1, implementations that confuse row movement with column movement can produce incorrect answers. This implementation explicitly adds or subtracts n for "DOWN" and "UP", ensuring accurate row transitions.
A third edge case involves returning to the starting position after several moves. For example, a sequence like ["DOWN", "UP"] should end at 0. Because the algorithm maintains a running position total and applies inverse operations correctly, it naturally handles these cases without special logic.
Finally, very small grids such as n = 2 can expose off-by-one mistakes in coordinate calculations. Since this implementation avoids explicit coordinate conversion entirely and directly updates the flattened index, it avoids many common indexing bugs.