885. Spiral Matrix III
Problem Description
You are given a grid with dimensions rows x cols
and a starting position at cell (rStart, cStart)
. Your task is to traverse the entire grid in a clockwise spiral pattern, starting from the given position and initially facing east.
The traversal follows these rules:
- You move in a clockwise spiral: first east, then south, then west, then north, and repeat this pattern
- The spiral expands outward from your starting position
- When your path takes you outside the grid boundaries, you continue walking but only record positions that are within the valid grid (positions where
0 <= row < rows
and0 <= col < cols
) - You must visit every cell in the grid exactly once
The grid's coordinate system works as follows:
- The northwest (top-left) corner is at position
(0, 0)
- The southeast (bottom-right) corner is at position
(rows-1, cols-1)
Your goal is to return a list of coordinates [[r0, c0], [r1, c1], ...]
representing all grid positions in the order they were visited during the spiral walk.
For example, if you start at the center of a grid and spiral outward, you would move:
- One step east
- One step south
- Two steps west
- Two steps north
- Three steps east
- Three steps south ... and so on, expanding the spiral with each complete rotation
The challenge is to correctly implement this expanding spiral pattern while keeping track of which positions fall within the grid boundaries.
Intuition
The key insight is recognizing the pattern of movement in a clockwise spiral. When we trace out a spiral path, we notice that we move in groups of steps, and each group follows a specific pattern:
- Move right for
k
steps - Move down for
k
steps - Move left for
k+1
steps - Move up for
k+1
steps
After completing these four directions, we've made one complete "loop" of the spiral. The crucial observation is that after each complete loop, the number of steps increases by 2 for the next loop. This is because we need to cover a larger perimeter as the spiral expands outward.
Starting with k = 1
:
- First loop: right 1, down 1, left 2, up 2
- Second loop: right 3, down 3, left 4, up 4
- Third loop: right 5, down 5, left 6, up 6
- And so on...
Why does this pattern work? Think about drawing a spiral on paper - after moving right and down by the same amount, you need to move one extra step left (to go past your starting column) and one extra step up (to go past your starting row) to complete the rectangle and position yourself for the next larger rectangle.
The direction changes can be represented as changes in row and column indices:
- Right:
(0, 1)
- row stays same, column increases - Down:
(1, 0)
- row increases, column stays same - Left:
(0, -1)
- row stays same, column decreases - Up:
(-1, 0)
- row decreases, column stays same
Since we might walk outside the grid boundaries during our spiral, we only record positions when they satisfy 0 <= row < rows
and 0 <= col < cols
. We continue walking the spiral pattern regardless of whether we're inside or outside the grid, ensuring we eventually cover all cells within the grid.
The algorithm naturally terminates when we've collected exactly rows * cols
valid positions, meaning we've visited every cell in the grid exactly once.
Solution Approach
We implement the spiral traversal using a systematic approach that tracks our current position and manages the expanding spiral pattern.
Initialization:
- Start with
ans = [[rStart, cStart]]
to record our starting position - Handle the edge case where the grid has only one cell by returning immediately
- Initialize
k = 1
to represent the number of steps in the first two directions of our spiral
Main Loop Structure:
The algorithm uses an infinite while True
loop that continues until we've visited all cells. Inside this loop, we iterate through four directions for each complete spiral rotation:
for dr, dc, dk in [[0, 1, k], [1, 0, k], [0, -1, k + 1], [-1, 0, k + 1]]:
This list encodes:
[0, 1, k]
: Move right - row change = 0, column change = 1, fork
steps[1, 0, k]
: Move down - row change = 1, column change = 0, fork
steps[0, -1, k+1]
: Move left - row change = 0, column change = -1, fork+1
steps[-1, 0, k+1]
: Move up - row change = -1, column change = 0, fork+1
steps
Step-by-Step Movement: For each direction, we take the specified number of steps:
for _ in range(dk):
rStart += dr
cStart += dc
After each step, we check if the new position is within the grid boundaries:
if 0 <= rStart < rows and 0 <= cStart < cols: ans.append([rStart, cStart])
We only record positions that are valid grid cells. Positions outside the grid are skipped, but we continue walking the spiral pattern.
Termination Condition: After adding each valid position, we check if we've collected all cells:
if len(ans) == rows * cols:
return ans
Once we've visited exactly rows * cols
positions, we've covered the entire grid and can return the result.
Spiral Expansion:
After completing each full rotation (all four directions), we increment k
by 2:
k += 2
This ensures the spiral expands properly - the next rotation will have 2 more steps in each direction compared to the previous rotation.
The beauty of this approach is its simplicity - we don't need complex state management or direction tracking. The pattern [k, k, k+1, k+1]
naturally creates the expanding spiral, and the boundary checking ensures we only record valid positions while maintaining the correct traversal order.
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 walk through a small example with a 3x3 grid starting at position (1, 1) (the center).
Grid: Starting at (1,1): [0,0] [0,1] [0,2] . . . [1,0] [1,1] [1,2] . X . [2,0] [2,1] [2,2] . . .
Initial State:
- Position: (1, 1)
ans = [[1, 1]]
(starting position recorded)k = 1
(initial step count)
First Spiral Loop (k=1):
-
Move Right 1 step: (0, 1, k=1)
- (1,1) → (1,2)
- Valid position ✓, add [1,2]
ans = [[1,1], [1,2]]
-
Move Down 1 step: (1, 0, k=1)
- (1,2) → (2,2)
- Valid position ✓, add [2,2]
ans = [[1,1], [1,2], [2,2]]
-
Move Left 2 steps: (0, -1, k+1=2)
- Step 1: (2,2) → (2,1)
- Valid ✓, add [2,1]
- Step 2: (2,1) → (2,0)
- Valid ✓, add [2,0]
ans = [[1,1], [1,2], [2,2], [2,1], [2,0]]
- Step 1: (2,2) → (2,1)
-
Move Up 2 steps: (-1, 0, k+1=2)
- Step 1: (2,0) → (1,0)
- Valid ✓, add [1,0]
- Step 2: (1,0) → (0,0)
- Valid ✓, add [0,0]
ans = [[1,1], [1,2], [2,2], [2,1], [2,0], [1,0], [0,0]]
- Step 1: (2,0) → (1,0)
After first loop: k = 3
(incremented by 2)
Second Spiral Loop (k=3):
- Move Right 3 steps: (0, 1, k=3)
- Step 1: (0,0) → (0,1)
- Valid ✓, add [0,1]
- Step 2: (0,1) → (0,2)
- Valid ✓, add [0,2]
ans = [[1,1], [1,2], [2,2], [2,1], [2,0], [1,0], [0,0], [0,1], [0,2]]
len(ans) = 9 = 3×3
✓ All cells visited! - Step 1: (0,0) → (0,1)
Final Path Visualization:
Order of visits: 7 8 9 [0,0] [0,1] [0,2] 6 1 2 → [1,0] [1,1] [1,2] 5 4 3 [2,0] [2,1] [2,2]
The spiral expands outward from the center, visiting all 9 cells in the correct clockwise pattern. Notice how the algorithm continues walking even when it would go outside the grid (which doesn't happen in this centered example), but only records valid positions.
Solution Implementation
1from typing import List
2
3
4class Solution:
5 def spiralMatrixIII(
6 self, rows: int, cols: int, rStart: int, cStart: int
7 ) -> List[List[int]]:
8 """
9 Generate coordinates in spiral order starting from (rStart, cStart).
10
11 Args:
12 rows: Number of rows in the matrix
13 cols: Number of columns in the matrix
14 rStart: Starting row position
15 cStart: Starting column position
16
17 Returns:
18 List of coordinates [row, col] in spiral order
19 """
20 # Initialize result with starting position
21 result = [[rStart, cStart]]
22
23 # Early return for single cell matrix
24 if rows * cols == 1:
25 return result
26
27 # Current position
28 current_row = rStart
29 current_col = cStart
30
31 # Steps to take in each direction (starts with 1)
32 steps_count = 1
33
34 # Continue spiraling until all cells are visited
35 while True:
36 # Define movement pattern: right, down, left, up
37 # Each iteration of spiral has 4 movements
38 directions = [
39 (0, 1, steps_count), # Move right: steps_count cells
40 (1, 0, steps_count), # Move down: steps_count cells
41 (0, -1, steps_count + 1), # Move left: (steps_count + 1) cells
42 (-1, 0, steps_count + 1) # Move up: (steps_count + 1) cells
43 ]
44
45 for row_delta, col_delta, num_steps in directions:
46 # Move in current direction for specified number of steps
47 for _ in range(num_steps):
48 current_row += row_delta
49 current_col += col_delta
50
51 # Check if current position is within matrix bounds
52 if 0 <= current_row < rows and 0 <= current_col < cols:
53 result.append([current_row, current_col])
54
55 # Return when all cells have been visited
56 if len(result) == rows * cols:
57 return result
58
59 # Increase step count for next spiral layer
60 # Each complete spiral increases the step count by 2
61 steps_count += 2
62
1class Solution {
2 public int[][] spiralMatrixIII(int rows, int cols, int rStart, int cStart) {
3 // Total number of cells in the matrix
4 int totalCells = rows * cols;
5
6 // Result array to store coordinates of visited cells
7 int[][] result = new int[totalCells][2];
8
9 // Add starting position as the first visited cell
10 result[0] = new int[] {rStart, cStart};
11
12 // Handle edge case: matrix with only one cell
13 if (totalCells == 1) {
14 return result;
15 }
16
17 // Current position
18 int currentRow = rStart;
19 int currentCol = cStart;
20 int resultIndex = 1;
21
22 // Spiral outward with increasing step sizes
23 // Step size increases by 2 each complete rotation (1, 1, 3, 3, 5, 5, ...)
24 for (int stepSize = 1; ; stepSize += 2) {
25 // Define directions: right, down, left, up
26 // Each direction has: row delta, column delta, and number of steps
27 int[][] directions = new int[][] {
28 {0, 1, stepSize}, // Move right for 'stepSize' steps
29 {1, 0, stepSize}, // Move down for 'stepSize' steps
30 {0, -1, stepSize + 1}, // Move left for 'stepSize + 1' steps
31 {-1, 0, stepSize + 1} // Move up for 'stepSize + 1' steps
32 };
33
34 // Process each direction in the current spiral layer
35 for (int[] direction : directions) {
36 int rowDelta = direction[0];
37 int colDelta = direction[1];
38 int steps = direction[2];
39
40 // Move in the current direction for the specified number of steps
41 while (steps > 0) {
42 currentRow += rowDelta;
43 currentCol += colDelta;
44
45 // Check if current position is within matrix bounds
46 if (currentRow >= 0 && currentRow < rows &&
47 currentCol >= 0 && currentCol < cols) {
48 // Add valid position to result
49 result[resultIndex] = new int[] {currentRow, currentCol};
50 resultIndex++;
51
52 // Check if all cells have been visited
53 if (resultIndex == totalCells) {
54 return result;
55 }
56 }
57
58 steps--;
59 }
60 }
61 }
62 }
63}
64
1class Solution {
2public:
3 vector<vector<int>> spiralMatrixIII(int rows, int cols, int rStart, int cStart) {
4 // Total number of cells in the matrix
5 int totalCells = rows * cols;
6
7 // Result vector to store coordinates in spiral order
8 vector<vector<int>> result;
9
10 // Add starting position
11 result.push_back({rStart, cStart});
12
13 // Early return if matrix has only one cell
14 if (totalCells == 1) {
15 return result;
16 }
17
18 // Spiral outward with increasing step sizes
19 // k represents the number of steps in each direction (increases by 2 each cycle)
20 for (int stepSize = 1; ; stepSize += 2) {
21 // Define directions: right, down, left, up
22 // Each direction contains: {row_delta, col_delta, number_of_steps}
23 // Right and down use stepSize steps, left and up use stepSize+1 steps
24 vector<vector<int>> directions = {
25 {0, 1, stepSize}, // Move right
26 {1, 0, stepSize}, // Move down
27 {0, -1, stepSize + 1}, // Move left
28 {-1, 0, stepSize + 1} // Move up
29 };
30
31 // Process each direction in the current spiral layer
32 for (auto& direction : directions) {
33 int rowDelta = direction[0];
34 int colDelta = direction[1];
35 int stepsInDirection = direction[2];
36
37 // Move in the current direction for the specified number of steps
38 while (stepsInDirection-- > 0) {
39 // Update current position
40 rStart += rowDelta;
41 cStart += colDelta;
42
43 // Check if current position is within matrix bounds
44 if (rStart >= 0 && rStart < rows && cStart >= 0 && cStart < cols) {
45 result.push_back({rStart, cStart});
46
47 // Return if we've visited all cells
48 if (result.size() == totalCells) {
49 return result;
50 }
51 }
52 }
53 }
54 }
55 }
56};
57
1/**
2 * Generates coordinates in a spiral pattern starting from a given position
3 * until all cells in the grid are visited.
4 *
5 * @param rows - Number of rows in the grid
6 * @param cols - Number of columns in the grid
7 * @param rStart - Starting row position
8 * @param cStart - Starting column position
9 * @returns Array of coordinates [row, col] in spiral order
10 */
11function spiralMatrixIII(rows: number, cols: number, rStart: number, cStart: number): number[][] {
12 // Direction vectors: right, down, left, up
13 const directions: number[][] = [
14 [0, 1], // right
15 [1, 0], // down
16 [0, -1], // left
17 [-1, 0] // up
18 ];
19
20 // Initialize current position and tracking variables
21 let currentCol: number = cStart;
22 let currentRow: number = rStart;
23 let directionIndex: number = 0; // Index for current direction
24 let stepsInCurrentDirection: number = 0; // Number of steps to take in current direction
25
26 // Result array starting with initial position
27 const result: number[][] = [[rStart, cStart]];
28 const totalCells: number = rows * cols;
29
30 // Continue until all cells in the grid are visited
31 while (result.length < totalCells) {
32 // Increase step count after completing two directions (right-down or left-up)
33 // This creates the expanding spiral pattern
34 if (directionIndex % 2 === 0) {
35 stepsInCurrentDirection++;
36 }
37
38 // Move in the current direction for the required number of steps
39 for (let step = 0; result.length < totalCells && step < stepsInCurrentDirection; step++) {
40 // Update position based on current direction
41 currentRow += directions[directionIndex][0];
42 currentCol += directions[directionIndex][1];
43
44 // Only add valid positions within grid boundaries
45 if (currentRow >= 0 && currentRow < rows &&
46 currentCol >= 0 && currentCol < cols) {
47 result.push([currentRow, currentCol]);
48 }
49 }
50
51 // Rotate to next direction (clockwise)
52 directionIndex = (directionIndex + 1) % 4;
53 }
54
55 return result;
56}
57
Time and Space Complexity
Time Complexity: O(max(rows, cols)²)
The algorithm generates a spiral pattern starting from (rStart, cStart)
and expanding outward. In each iteration of the outer while loop, the variable k
increases by 2, and the spiral moves in 4 directions (right, down, left, up) with steps of k, k, k+1, k+1
respectively.
- The spiral continues until all
rows * cols
cells are visited - In the worst case, the starting position is at a corner of the grid, requiring the spiral to expand to cover the entire grid
- The maximum distance from any starting point to cover all cells is proportional to
max(rows, cols)
- For each complete square of the spiral, we visit
O(k)
cells wherek
goes from 1 to approximately2 * max(rows, cols)
- Total iterations:
1 + 3 + 5 + 7 + ... + (2 * max(rows, cols) - 1)
=O(max(rows, cols)²)
Even though we only store valid cells (those within bounds), we still need to traverse all positions in the spiral pattern, including those outside the grid boundaries.
Space Complexity: O(rows * cols)
The space complexity is determined by the output array ans
which stores all valid positions within the grid. Since we store exactly rows * cols
positions (each cell of the grid exactly once), the space complexity is O(rows * cols)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Spiral Step Pattern
One of the most common mistakes is getting the step count pattern wrong. Many developers incorrectly assume the spiral should use the same number of steps in all four directions, like [k, k, k, k]
, or might try [k, k+1, k, k+1]
.
Why it fails: The correct pattern is [k, k, k+1, k+1]
. This is because after moving right and down by k
steps, we need to move left and up by k+1
steps to complete the rectangular spiral layer. This ensures proper expansion of the spiral.
Solution:
# Correct pattern directions = [ (0, 1, steps_count), # Right: k steps (1, 0, steps_count), # Down: k steps (0, -1, steps_count + 1), # Left: k+1 steps (-1, 0, steps_count + 1) # Up: k+1 steps ]
2. Modifying Loop Variables Inside Nested Loops
Another pitfall is accidentally using the same variable names in nested loops or modifying the position variables incorrectly:
# WRONG - modifying rStart and cStart directly loses original reference
for dr, dc, dk in directions:
for _ in range(dk):
rStart += dr # This modifies the parameter directly
cStart += dc
Why it fails: While this works in Python (parameters are local variables), it makes the code harder to understand and maintain. Using descriptive variable names like current_row
and current_col
makes the intent clearer.
3. Off-by-One Error in Boundary Checking
A subtle mistake is using incorrect boundary conditions:
# WRONG - uses <= for upper bounds if 0 <= current_row <= rows and 0 <= current_col <= cols: result.append([current_row, current_col])
Why it fails: Grid indices are 0-based, so valid row indices are from 0 to rows-1
, not 0 to rows
. The condition should be current_row < rows
and current_col < cols
.
4. Forgetting to Handle Single-Cell Grid
While not strictly necessary (the main loop will handle it), forgetting the optimization for a 1x1 grid can lead to unnecessary computation:
# Missing optimization result = [[rStart, cStart]] # Directly entering the while loop without checking rows * cols == 1
Why it's suboptimal: For a single-cell grid, we already have the complete answer after initialization. The early return if rows * cols == 1: return result
avoids entering the loop unnecessarily.
5. Incorrect Spiral Expansion Rate
Some implementations forget to increment the step count properly:
# WRONG - incrementing by 1 instead of 2 steps_count += 1 # WRONG - forgetting to increment at all # (no increment after completing 4 directions)
Why it fails: The spiral must expand by 2 steps after each complete rotation (all 4 directions). This is because after completing a full rectangular layer, the next layer needs one additional step on each side compared to the previous layer, resulting in a total increase of 2 steps per complete spiral.
Which of these pictures shows the visit order of a depth-first search?
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!