Facebook Pixel

59. Spiral Matrix II

MediumArrayMatrixSimulation
Leetcode Link

Problem Description

You need to create a square matrix of size n x n and fill it with numbers from 1 to 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 and k+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:

  1. Fill the current position with the next number
  2. Try to move in the current direction
  3. If we can't (out of bounds or cell already filled), turn clockwise
  4. 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 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 matrix ans 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] and dirs[k+1] give the row and column increments for direction k
  • 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 :

  • 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
  • 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 as True
  • Using (k + 1) % 4 ensures the direction index wraps around from 3 back to 0
  • The algorithm naturally terminates after filling all 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 Evaluator

Example 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 . The main loop iterates exactly 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More