Problem
Given an m ร n grid of integers, return all of its values in spiral order โ starting from the top-left corner, sweeping right, then down, then left, then up, and repeating inward until every cell is visited.
Example:
- Input:
matrix = [[1,2,3],[4,5,6],[7,8,9]] - Output:
[1, 2, 3, 6, 9, 8, 7, 4, 5] - Explanation: Sweep right across row 0, down column 2, left across row 2, up column 0, then the center cell.
A 1ร1 matrix [[42]] returns [42] โ the loop handles this without any special case.
Intuition
A spiral follows a repeating four-step rhythm: right โ down โ left โ up. After completing each edge, that row or column is "used up" and the active region shrinks by one layer. Tracking four boundary pointers and shrinking them immediately after each pass lets you traverse every cell exactly once without any visited bookkeeping.
Approach 1 โ Direction Rotation
Simulate the spiral by cycling through four direction vectors. Move in the current direction until the next step would exit the grid or revisit a cell, then rotate 90ยฐ clockwise.
Steps:
- Define four direction vectors: right, down, left, up
- Allocate a
visitedboolean grid the same size asmatrix - For each of the
m ร nelements, append the current cell and mark it visited - Compute the next cell in the current direction
- If next cell is out of bounds or visited, rotate to the next direction before moving
1def spiralOrder(matrix):
2 rows, cols = len(matrix), len(matrix[0])
3 visited = [[False] * cols for _ in range(rows)]
4 directions = [(0, 1), (1, 0), (0, -1), (-1, 0)] # right, down, left, up
5 dir_index = 0
6 row, col = 0, 0
7 result = []
8
9 for _ in range(rows * cols):
10 result.append(matrix[row][col])
11 visited[row][col] = True
12
13 next_row = row + directions[dir_index][0]
14 next_col = col + directions[dir_index][1]
15
16 # Rotate direction if next cell is out of bounds or already visited
17 if (next_row < 0 or next_row >= rows or
18 next_col < 0 or next_col >= cols or
19 visited[next_row][next_col]):
20 dir_index = (dir_index + 1) % 4
21 next_row = row + directions[dir_index][0]
22 next_col = col + directions[dir_index][1]
23
24 row, col = next_row, next_col
25
26 return result- Time: O(m ร n) โ every cell is visited exactly once
- Space: O(m ร n) โ the
visitedgrid mirrors the input dimensions
Approach 2 โ Boundary Simulation (Optimal)
Track four boundary pointers (top, bottom, left, right) defining the unvisited region. Walk each edge in spiral order, then immediately shrink the corresponding boundary. No visited array needed.
Steps:
- Initialize
top = 0,bottom = rows - 1,left = 0,right = cols - 1 - Sweep left-to-right along the
toprow; incrementtop - Sweep top-to-bottom along the
rightcolumn; decrementright - If rows remain (
top <= bottom), sweep right-to-left along thebottomrow; decrementbottom - If columns remain (
left <= right), sweep bottom-to-top along theleftcolumn; incrementleft - Repeat until
top > bottomorleft > right
1def spiralOrder(matrix):
2 result = []
3 top, bottom = 0, len(matrix) - 1
4 left, right = 0, len(matrix[0]) - 1
5
6 while top <= bottom and left <= right:
7 for col in range(left, right + 1):
8 result.append(matrix[top][col])
9 top += 1 # shrink before the downward pass so the top-right corner isn't revisited
10
11 for row in range(top, bottom + 1):
12 result.append(matrix[row][right])
13 right -= 1
14
15 if top <= bottom: # guard: a single remaining row has no reverse pass
16 for col in range(right, left - 1, -1):
17 result.append(matrix[bottom][col])
18 bottom -= 1
19
20 if left <= right: # guard: a single remaining column has no upward pass
21 for row in range(bottom, top - 1, -1):
22 result.append(matrix[row][left])
23 left += 1
24
25 return result- Time: O(m ร n) โ every cell is visited exactly once
- Space: O(1) extra โ only four integer boundary variables beyond the output array
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Direction Rotation | O(m ร n) | O(m ร n) | When the visited-flag logic feels more natural and memory isn't constrained |
| Boundary Simulation | O(m ร n) | O(1) | Default choice โ identical speed, no extra memory |
Common Mistakes
- Missing the
top <= bottomguard before the reverse bottom-row pass: When one row remains after the rightward sweep, omitting this check causes the same cells to be swept again right-to-left. A single-row matrix[[1,2,3]]produces[1,2,3,3,2,1]without this guard. - Missing the
left <= rightguard before the upward left-column pass: Same issue with a single remaining column โ the upward pass re-visits every cell just added by the downward pass. - Not incrementing
topbefore the downward pass: The boundary must be shrunk immediately after each side is consumed. Iftopis still at the old value when the rightward column pass starts,matrix[top][right](the top-right corner) is added twice. - Sweeping the bottom row left-to-right instead of right-to-left: The spiral direction is
โ โ โ โ; reversing the bottom-row direction produces a rotation of the correct answer, not a spiral. - Assuming the matrix is square: Using a single
nfor both dimensions silently produces incorrect boundaries on any rectangular input like[[1,2,3],[4,5,6]].
Related Problems
rotate-imageโ in-place 90ยฐ rotation uses the same layer-by-layer boundary thinkingset-matrix-zeroesโ in-place matrix modification that requires careful traversal to avoid corrupting later readssearch-a-2d-matrixโ exploits 2D grid structure with coordinate arithmetic instead of full traversalgame-of-lifeโ grid simulation where update rules depend on traversal order, similar to spiral's direction disciplinevalid-sudokuโ structured traversal of fixed-size regions within a 2D grid