59. Spiral Matrix II
Problem Description
You need to create a square matrix of size n x n
and fill it with numbers from 1
to n²
following a spiral pattern.
The spiral pattern means you start filling from the top-left corner and move:
- First, go right across the first row
- Then, go down along the last column
- Then, go left across the bottom row
- Then, go up along the first column
- Continue this spiral pattern inward until all positions are filled
For example, if n = 3
, you would create a 3x3 matrix and fill it with numbers 1 through 9 in spiral order:
[1, 2, 3] [8, 9, 4] [7, 6, 5]
The filling path would be: start at position (0,0) with 1, move right to fill 2 and 3, then move down to fill 4 and 5, then move left to fill 6 and 7, then move up to fill 8, and finally move right to fill 9.
The solution uses a simulation approach with direction vectors. The dirs
tuple (0, 1, 0, -1, 0)
encodes four directions: right (0,1), down (1,0), left (0,-1), and up (-1,0). The algorithm fills each position with incrementing values and changes direction whenever it hits a boundary or an already-filled cell.
Intuition
When we think about filling a matrix in spiral order, we can imagine walking along the edges of the matrix, always keeping the unfilled area to our right. It's like walking along the walls of a rectangular room, gradually spiraling inward.
The key insight is that we're essentially following a repeating pattern of four directions: right → down → left → up. We continue in one direction until we can't go further (either hitting a boundary or an already-filled cell), then we turn 90 degrees clockwise.
To implement this, we can use a clever trick with direction vectors. Instead of using separate if-else statements for each direction, we can encode all four directions in a single array. The pattern (0, 1, 0, -1, 0)
gives us:
- Taking indices
k
andk+1
: (0,1) means right, (1,0) means down, (0,-1) means left, (-1,0) means up - When we need to turn, we just increment
k
by 1 (modulo 4 to wrap around)
The beauty of this approach is that we don't need to track complex boundary conditions for each layer of the spiral. We simply:
- Fill the current position with the next number
- Try to move in the current direction
- If we can't (out of bounds or cell already filled), turn clockwise
- Move to the next position
This natural "bump and turn" behavior automatically creates the spiral pattern, as the already-filled cells act like walls that guide our path inward. The algorithm naturally terminates when we've filled all n²
positions.
Solution Approach
We implement the spiral matrix generation through simulation, following these steps:
1. Initialize the matrix and variables:
- Create an
n x n
matrixans
filled with zeros:ans = [[0] * n for _ in range(n)]
- Set position variables
i = j = 0
to start at the top-left corner (0, 0) - Define direction array
dirs = (0, 1, 0, -1, 0)
to encode four directions - Initialize direction index
k = 0
to start moving right
2. Direction encoding:
The dirs
array cleverly encodes all four directions:
dirs[k]
anddirs[k+1]
give the row and column increments for directionk
- When
k=0
:(dirs[0], dirs[1]) = (0, 1)
→ move right - When
k=1
:(dirs[1], dirs[2]) = (1, 0)
→ move down - When
k=2
:(dirs[2], dirs[3]) = (0, -1)
→ move left - When
k=3
:(dirs[3], dirs[4]) = (-1, 0)
→ move up
3. Fill the matrix iteratively:
For each value v
from 1
to n²
:
- Fill current position:
ans[i][j] = v
- Calculate next position:
x, y = i + dirs[k], j + dirs[k + 1]
- Check if we need to turn:
- If
x < 0 or x >= n or y < 0 or y >= n
(out of bounds) - Or if
ans[x][y]
is non-zero (already filled) - Then rotate clockwise:
k = (k + 1) % 4
- If
- Move to the next position:
i, j = i + dirs[k], j + dirs[k + 1]
4. Key implementation details:
- The condition
ans[x][y]
works because Python treats non-zero values asTrue
- Using
(k + 1) % 4
ensures the direction index wraps around from 3 back to 0 - The algorithm naturally terminates after filling all
n²
positions - No need to explicitly track spiral layers or complex boundary conditions
The time complexity is O(n²)
as we visit each cell exactly once, and the space complexity is O(n²)
for storing the result matrix.
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 creating a 3×3 spiral matrix step by step.
Initial Setup:
- Matrix:
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
- Position:
(i, j) = (0, 0)
- Direction array:
dirs = (0, 1, 0, -1, 0)
- Direction index:
k = 0
(moving right)
Step 1: Fill value 1
- Fill
ans[0][0] = 1
:[[1, 0, 0], [0, 0, 0], [0, 0, 0]]
- Next position:
(0+0, 0+1) = (0, 1)
- valid and empty - Move to
(0, 1)
Step 2: Fill value 2
- Fill
ans[0][1] = 2
:[[1, 2, 0], [0, 0, 0], [0, 0, 0]]
- Next position:
(0+0, 1+1) = (0, 2)
- valid and empty - Move to
(0, 2)
Step 3: Fill value 3
- Fill
ans[0][2] = 3
:[[1, 2, 3], [0, 0, 0], [0, 0, 0]]
- Next position:
(0+0, 2+1) = (0, 3)
- out of bounds! - Turn clockwise:
k = 1
(now moving down) - New next position:
(0+1, 2+0) = (1, 2)
- Move to
(1, 2)
Step 4: Fill value 4
- Fill
ans[1][2] = 4
:[[1, 2, 3], [0, 0, 4], [0, 0, 0]]
- Next position:
(1+1, 2+0) = (2, 2)
- valid and empty - Move to
(2, 2)
Step 5: Fill value 5
- Fill
ans[2][2] = 5
:[[1, 2, 3], [0, 0, 4], [0, 0, 5]]
- Next position:
(2+1, 2+0) = (3, 2)
- out of bounds! - Turn clockwise:
k = 2
(now moving left) - New next position:
(2+0, 2-1) = (2, 1)
- Move to
(2, 1)
Step 6: Fill value 6
- Fill
ans[2][1] = 6
:[[1, 2, 3], [0, 0, 4], [0, 6, 5]]
- Next position:
(2+0, 1-1) = (2, 0)
- valid and empty - Move to
(2, 0)
Step 7: Fill value 7
- Fill
ans[2][0] = 7
:[[1, 2, 3], [0, 0, 4], [7, 6, 5]]
- Next position:
(2+0, 0-1) = (2, -1)
- out of bounds! - Turn clockwise:
k = 3
(now moving up) - New next position:
(2-1, 0+0) = (1, 0)
- Move to
(1, 0)
Step 8: Fill value 8
- Fill
ans[1][0] = 8
:[[1, 2, 3], [8, 0, 4], [7, 6, 5]]
- Next position:
(1-1, 0+0) = (0, 0)
- already filled! - Turn clockwise:
k = 0
(now moving right again) - New next position:
(1+0, 0+1) = (1, 1)
- Move to
(1, 1)
Step 9: Fill value 9
- Fill
ans[1][1] = 9
:[[1, 2, 3], [8, 9, 4], [7, 6, 5]]
- All positions filled!
Final Result:
[1, 2, 3] [8, 9, 4] [7, 6, 5]
The path forms a clockwise spiral: starting from the top-left, moving right along the top edge, down the right edge, left along the bottom edge, up the left edge, and finally filling the center.
Solution Implementation
1class Solution:
2 def generateMatrix(self, n: int) -> List[List[int]]:
3 """
4 Generate an n x n matrix filled with elements from 1 to n² in spiral order.
5
6 Args:
7 n: The size of the square matrix
8
9 Returns:
10 A 2D list representing the spiral matrix
11 """
12 # Initialize n x n matrix filled with zeros
13 matrix = [[0] * n for _ in range(n)]
14
15 # Direction vectors: right, down, left, up
16 # Using pairs: (row_delta, col_delta) for each direction
17 directions = [0, 1, 0, -1, 0] # Compact representation of (0,1), (1,0), (0,-1), (-1,0)
18
19 # Starting position and direction index
20 row = col = 0 # Start at top-left corner
21 direction_index = 0 # Start moving right
22
23 # Fill the matrix with values from 1 to n²
24 for value in range(1, n * n + 1):
25 # Place current value at current position
26 matrix[row][col] = value
27
28 # Calculate next position based on current direction
29 next_row = row + directions[direction_index]
30 next_col = col + directions[direction_index + 1]
31
32 # Check if we need to change direction:
33 # - If next position is out of bounds
34 # - If next position is already filled (non-zero)
35 if (next_row < 0 or next_row >= n or
36 next_col < 0 or next_col >= n or
37 matrix[next_row][next_col] != 0):
38 # Turn clockwise (move to next direction)
39 direction_index = (direction_index + 1) % 4
40
41 # Move to next position using current (possibly updated) direction
42 row += directions[direction_index]
43 col += directions[direction_index + 1]
44
45 return matrix
46
1class Solution {
2 public int[][] generateMatrix(int n) {
3 // Initialize n x n matrix with zeros
4 int[][] matrix = new int[n][n];
5
6 // Direction vectors: right, down, left, up
7 // dirs[i] and dirs[i+1] represent row and column deltas for each direction
8 final int[] directions = {0, 1, 0, -1, 0};
9
10 // Current position (row, col) and direction index
11 int currentRow = 0;
12 int currentCol = 0;
13 int directionIndex = 0; // 0: right, 1: down, 2: left, 3: up
14
15 // Fill the matrix with values from 1 to n*n in spiral order
16 for (int value = 1; value <= n * n; value++) {
17 // Place current value at current position
18 matrix[currentRow][currentCol] = value;
19
20 // Calculate next position based on current direction
21 int nextRow = currentRow + directions[directionIndex];
22 int nextCol = currentCol + directions[directionIndex + 1];
23
24 // Check if we need to change direction:
25 // - Out of bounds (nextRow or nextCol is negative or >= n)
26 // - Cell already filled (matrix[nextRow][nextCol] != 0)
27 if (nextRow < 0 || nextRow >= n || nextCol < 0 || nextCol >= n ||
28 matrix[nextRow][nextCol] != 0) {
29 // Turn clockwise to next direction
30 directionIndex = (directionIndex + 1) % 4;
31 }
32
33 // Move to next position using current (possibly updated) direction
34 currentRow += directions[directionIndex];
35 currentCol += directions[directionIndex + 1];
36 }
37
38 return matrix;
39 }
40}
41
1class Solution {
2public:
3 vector<vector<int>> generateMatrix(int n) {
4 // Initialize n x n matrix filled with zeros
5 vector<vector<int>> matrix(n, vector<int>(n, 0));
6
7 // Direction vectors: right(0,1), down(1,0), left(0,-1), up(-1,0)
8 // Using array format: [rowDelta1, colDelta1, rowDelta2, colDelta2, ...]
9 const int directions[5] = {0, 1, 0, -1, 0};
10
11 // Current position (row, col) and direction index
12 int currentRow = 0;
13 int currentCol = 0;
14 int directionIndex = 0; // 0=right, 1=down, 2=left, 3=up
15
16 // Fill the matrix with values from 1 to n*n in spiral order
17 for (int value = 1; value <= n * n; ++value) {
18 // Place current value at current position
19 matrix[currentRow][currentCol] = value;
20
21 // Calculate next position based on current direction
22 int nextRow = currentRow + directions[directionIndex];
23 int nextCol = currentCol + directions[directionIndex + 1];
24
25 // Check if we need to change direction:
26 // - If next position is out of bounds
27 // - If next position is already filled (non-zero)
28 if (nextRow < 0 || nextRow >= n || nextCol < 0 || nextCol >= n ||
29 matrix[nextRow][nextCol] != 0) {
30 // Turn clockwise to next direction
31 directionIndex = (directionIndex + 1) % 4;
32 }
33
34 // Move to next position using current (possibly updated) direction
35 currentRow += directions[directionIndex];
36 currentCol += directions[directionIndex + 1];
37 }
38
39 return matrix;
40 }
41};
42
1/**
2 * Generates an n x n matrix filled with numbers from 1 to n² in spiral order
3 * @param n - The size of the square matrix
4 * @returns A 2D array filled in spiral order
5 */
6function generateMatrix(n: number): number[][] {
7 // Initialize n x n matrix filled with zeros
8 const matrix: number[][] = Array.from({ length: n }, () => Array(n).fill(0));
9
10 // Direction vectors for right, down, left, up movements
11 // dirs[i] and dirs[i+1] represent row and column increments respectively
12 const directionDeltas: number[] = [0, 1, 0, -1, 0];
13
14 // Current position and direction index
15 let currentRow: number = 0;
16 let currentCol: number = 0;
17 let directionIndex: number = 0;
18
19 // Fill the matrix with values from 1 to n²
20 for (let value: number = 1; value <= n * n; value++) {
21 // Place current value at current position
22 matrix[currentRow][currentCol] = value;
23
24 // Calculate next position based on current direction
25 const nextRow: number = currentRow + directionDeltas[directionIndex];
26 const nextCol: number = currentCol + directionDeltas[directionIndex + 1];
27
28 // Check if we need to change direction:
29 // - If next position is out of bounds
30 // - If next position is already filled (not zero)
31 if (nextRow < 0 || nextRow >= n || nextCol < 0 || nextCol >= n || matrix[nextRow][nextCol] !== 0) {
32 // Rotate direction clockwise (right -> down -> left -> up -> right)
33 directionIndex = (directionIndex + 1) % 4;
34 }
35
36 // Move to next position using current direction
37 currentRow += directionDeltas[directionIndex];
38 currentCol += directionDeltas[directionIndex + 1];
39 }
40
41 return matrix;
42}
43
Time and Space Complexity
Time Complexity: O(n²)
The algorithm fills an n × n
matrix with values from 1
to n²
. The main loop iterates exactly n²
times (from 1
to n * n + 1
), and in each iteration, it performs constant-time operations:
- Setting a value in the matrix:
O(1)
- Calculating next position:
O(1)
- Checking boundaries and visited cells:
O(1)
- Updating direction if needed:
O(1)
Therefore, the overall time complexity is O(n²)
.
Space Complexity: O(1)
(excluding the output array)
The algorithm uses only a fixed amount of extra space:
- Variables
i
,j
,k
for tracking position and direction:O(1)
- Variables
x
,y
for checking next position:O(1)
- The
dirs
tuple with 5 constant elements:O(1)
The answer matrix ans
of size n × n
requires O(n²)
space, but this is not counted in the space complexity analysis as it's part of the required output.
Therefore, the auxiliary space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Direction Change Logic
Issue: A common mistake is checking the boundary conditions and changing direction AFTER moving to the new position, rather than checking BEFORE moving.
Incorrect approach:
# WRONG: Move first, then check row += directions[direction_index] col += directions[direction_index + 1] if (row < 0 or row >= n or col < 0 or col >= n or matrix[row][col] != 0): # Too late! We've already moved to an invalid position direction_index = (direction_index + 1) % 4
Solution: Always validate the next position before moving. Calculate where you would go, check if it's valid, change direction if needed, then move.
Pitfall 2: Off-by-One Error in Direction Array Indexing
Issue: When using the compact direction array [0, 1, 0, -1, 0]
, forgetting that each direction uses two consecutive elements can lead to index out of bounds errors.
Incorrect approach:
directions = [0, 1, 0, -1, 0] # WRONG: Trying to access directions[4] and directions[5] when direction_index = 4 next_row = row + directions[direction_index] next_col = col + directions[direction_index + 1] # IndexError when direction_index = 4
Solution: Always use modulo operation when incrementing the direction index to ensure it wraps around properly: direction_index = (direction_index + 1) % 4
Pitfall 3: Forgetting to Check Already-Filled Cells
Issue: Only checking boundary conditions without verifying if a cell is already filled will cause the spiral to overwrite previously placed values.
Incorrect approach:
# WRONG: Only checking boundaries, not if cell is already filled if (next_row < 0 or next_row >= n or next_col < 0 or next_col >= n): direction_index = (direction_index + 1) % 4
Solution: Include a check for non-zero values (already filled cells) in your turn condition: matrix[next_row][next_col] != 0
Pitfall 4: Using Wrong Initial Values
Issue: Starting with the wrong value (0 instead of 1) or incorrect position leads to an incorrectly filled matrix.
Incorrect approach:
# WRONG: Starting from 0 instead of 1
for value in range(0, n * n): # Results in matrix filled with 0 to n²-1
matrix[row][col] = value
Solution: Ensure the loop starts from 1 and goes up to n²: range(1, n * n + 1)
Pitfall 5: Confusion with Direction Representation
Issue: Mixing up row and column increments when using different direction representations can cause the spiral to move incorrectly.
Alternative correct representations to avoid confusion:
# Method 1: Explicit direction vectors (clearer but more verbose) directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] # right, down, left, up dr, dc = directions[direction_index] # Method 2: Separate arrays for row and column changes row_dirs = [0, 1, 0, -1] col_dirs = [1, 0, -1, 0]
Solution: Pick one consistent representation and stick with it throughout your code. Add comments to clarify what each direction represents.
Which of these properties could exist for a graph but not a tree?
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!