54. Spiral Matrix - Explanation
Description
Given an m x n matrix of integers matrix, return a list of all elements within the matrix in spiral order.
Example 1:
Input: matrix = [[1,2],[3,4]]
Output: [1,2,4,3]Example 2:
Input: matrix = [[1,2,3],[4,5,6],[7,8,9]]
Output: [1,2,3,6,9,8,7,4,5]Example 3:
Input: matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
Output: [1,2,3,4,8,12,11,10,9,5,6,7]Constraints:
1 <= matrix.length, matrix[i].length <= 10-100 <= matrix[i][j] <= 100
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(m*n) time and O(1) extra space, where m is the number of rows and n is the number of columns in the given matrix.
Hint 1
Try to simulate the process as described in the problem. Think in terms of matrix layers, starting from the outermost boundaries and moving inward. Can you determine an efficient way to implement this?
Hint 2
Each boundary consists of four parts: the top row, right column, bottom row, and left column, which follow the spiral order and act as four pointers. For each layer, the top pointer increments by one, the right pointer decrements by one, the left pointer increments by one, and the bottom pointer decrements by one.
Hint 3
At each layer, four loops traverse the matrix: one moves left to right along the top row, another moves top to bottom along the right column, the next moves right to left along the bottom row, and the last moves bottom to top along the left column. This process generates the spiral order.
Prerequisites
Before attempting this problem, you should be comfortable with:
- 2D Array Traversal - Navigating through rows and columns of a matrix
- Boundary Tracking - Maintaining and shrinking top, bottom, left, right boundaries layer by layer
- Direction Vectors - Using coordinate deltas to control and rotate movement direction
- Recursion - Breaking down the spiral into smaller sub-problems (inner layers)
1. Recursion
Intuition
We want to print the matrix in spiral order (right → down → left → up, repeating).
This solution treats the spiral as a sequence of smaller and smaller “rings”.
Each ring can be described by:
- how many rows are left to cover
- how many columns are left to cover
- a current position
(r, c) - a direction
(dr, dc)that tells us where to move next
At each step, we do two things:
- Walk straight in the current direction and append all elements along that edge.
- Shrink the problem and rotate direction for the next edge.
After moving along one edge, the remaining unvisited area becomes a smaller rectangle, and the next direction is obtained by “turning right” (changing (dr, dc)).
Algorithm
- Keep an answer list
res. - Define a recursive function that takes:
row= remaining rows to processcol= remaining columns to process- current position
(r, c) - direction
(dr, dc)
- Base case:
- if
row == 0orcol == 0, stop (nothing left to traverse)
- if
- Move
colsteps in the current direction:- each step updates
(r, c)by(dr, dc) - append
matrix[r][c]tores
- each step updates
- Recursively solve the smaller sub-rectangle:
- swap the roles of
rowandcol(because after turning, width/height swap) - reduce the new width by
1(one side was fully consumed) - rotate the direction to turn right
- swap the roles of
- Start the recursion by moving right from just outside the matrix:
- position
(0, -1)with direction(0, 1)
- position
- Return
res.
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
m, n = len(matrix), len(matrix[0])
res = []
# append all the elements in the given direction
def dfs(row, col, r, c, dr, dc):
if row == 0 or col == 0:
return
for i in range(col):
r += dr
c += dc
res.append(matrix[r][c])
# sub-problem
dfs(col, row - 1, r, c, dc, -dr)
# start by going to the right
dfs(m, n, 0, -1, 0, 1)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- space for recursion stack.
- space for the output list.
Where is the number of rows and is the number of columns.
2. Iteration
Intuition
We want to traverse a matrix in spiral order:
right → down → left → up, repeatedly, moving inward layer by layer.
A clean iterative way to do this is to maintain four boundaries:
top→ the topmost unvisited rowbottom→ one past the bottommost unvisited rowleft→ the leftmost unvisited columnright→ one past the rightmost unvisited column
At each step, we walk along the current outer boundary in four directions:
- left → right across the top row
- top → bottom down the right column
- right → left across the bottom row
- bottom → top up the left column
After each pass, we shrink the boundaries inward.
Algorithm
- Initialize:
resas an empty listleft = 0,right = number of columnstop = 0,bottom = number of rows
- While there is still an unvisited rectangle (
left < rightandtop < bottom): - Traverse the top row from
lefttoright - 1and append elements.- Increment
top
- Increment
- Traverse the right column from
toptobottom - 1and append elements.- Decrement
right
- Decrement
- If the remaining rectangle is invalid, break (prevents duplicates).
- Traverse the bottom row from
right - 1down toleftand append elements.- Decrement
bottom
- Decrement
- Traverse the left column from
bottom - 1up totopand append elements.- Increment
left
- Increment
- Continue until all elements are added.
- Return
res.
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
left, right = 0, len(matrix[0])
top, bottom = 0, len(matrix)
while left < right and top < bottom:
for i in range(left, right):
res.append(matrix[top][i])
top += 1
for i in range(top, bottom):
res.append(matrix[i][right - 1])
right -= 1
if not (left < right and top < bottom):
break
for i in range(right - 1, left - 1, -1):
res.append(matrix[bottom - 1][i])
bottom -= 1
for i in range(bottom - 1, top - 1, -1):
res.append(matrix[i][left])
left += 1
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
Where is the number of rows and is the number of columns.
3. Iteration (Optimal)
Intuition
We want to read the matrix in spiral order: right → down → left → up, repeating.
Instead of keeping four boundaries (top, bottom, left, right), this approach tracks:
- the current direction (right, down, left, up)
- how many steps we can take in the current direction before turning
Key idea:
- Spiral traversal alternates between moving along a row length and a column length
- first we move right
colssteps - then down
rows - 1steps - then left
cols - 1steps - then up
rows - 2steps - and so on...
- first we move right
- After completing a direction, the available steps in that “dimension” shrink by 1.
We store the remaining step counts in an array:
steps[0]= how many moves left in the horizontal directionsteps[1]= how many moves left in the vertical direction
d & 1 tells us whether the current direction is horizontal (0) or vertical (1).
Algorithm
- Create a list of direction vectors in clockwise order:
- right
(0, 1), down(1, 0), left(0, -1), up(-1, 0)
- right
- Initialize step counts:
steps[0] = number of columnssteps[1] = number of rows - 1
- Start just outside the matrix at
(r, c) = (0, -1)so the first move goes into(0, 0). - Set direction index
d = 0(start moving right). - While the current step count
steps[d & 1]is greater than0:- Move
steps[d & 1]times in directiond:- update
(r, c)by the direction vector - append
matrix[r][c]to the result
- update
- After finishing those moves, shrink the step count for that dimension:
steps[d & 1] -= 1
- Turn to the next direction:
d = (d + 1) % 4
- Move
- Return the result list.
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
res = []
directions = [(0, 1), (1, 0), (0, -1), (-1, 0)]
steps = [len(matrix[0]), len(matrix) - 1]
r, c, d = 0, -1, 0
while steps[d & 1]:
for i in range(steps[d & 1]):
r += directions[d][0]
c += directions[d][1]
res.append(matrix[r][c])
steps[d & 1] -= 1
d += 1
d %= 4
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
Where is the number of rows and is the number of columns.
Common Pitfalls
Not Checking Boundaries After Each Direction
After completing a horizontal traversal, the vertical bounds may have crossed (or vice versa). Failing to check left < right && top < bottom before the third and fourth directions causes duplicate elements to be added when the matrix reduces to a single row or column.
Mishandling Non-Square Matrices
Rectangular matrices with significantly different row and column counts can cause issues if the algorithm assumes square behavior. The spiral may terminate early or add extra elements if boundary checks do not account for both dimensions independently.
Incorrect Direction Rotation
When using direction vectors, rotating incorrectly (e.g., counterclockwise instead of clockwise, or incorrect sign changes) produces a non-spiral traversal pattern. The correct clockwise rotation transforms (dr, dc) to (dc, -dr).
Sign in to join the discussion