Spiral Traversal of a Matrix
Spiral Traversal of a Matrix
Pattern:
Idea: 4 pointers top , bottom, left, right
Variations :
- part of Matrices Everywhere !!!
π» Code
def spiralOrder(matrix):
if not matrix:
return []
result = []
top = 0
bottom = len(matrix) - 1
left = 0
right = len(matrix[0]) - 1
while top <= bottom and left <= right:
# 1. Traverse the top row: left -> right
for col in range(left, right + 1):
result.append(matrix[top][col])
# This row has now been completely processed.
top += 1
# 2. Traverse the right column: top -> bottom
for row in range(top, bottom + 1):
result.append(matrix[row][right])
# This column has now been completely processed.
right -= 1
# 3. Traverse the bottom row: right -> left
#
# The boundary checks are important because the matrix
# may have only one remaining row.
if top <= bottom:
for col in range(right, left - 1, -1):
result.append(matrix[bottom][col])
bottom -= 1
# 4. Traverse the left column: bottom -> top
#
# Again, check the remaining boundaries because the matrix
# may have only one remaining column.
if left <= right:
for row in range(bottom, top - 1, -1):
result.append(matrix[row][left])
left += 1
return result
Time complexity - O(rows * cols)
Aux. Space complexity - O(1) , if we are printing , if output array then O(rows * cols)
Spiral Traversal of a Matrix
Tags: #Matrix #Array #Two-Pointers #Simulation #Boundary-Traversal #Four-Pointers #Layered-Traversal #Traversal #In-place #LC54 #LeetCode #FAANG
Problem Statement
Given an matrix, return all of its elements in spiral order.
The traversal follows:
-
Left β Right across the top row
-
Top β Bottom down the right column
-
Right β Left across the bottom row
-
Bottom β Top up the left column
Then move inward and repeat.
Example
matrix =
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
Spiral traversal:
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]
This is LeetCode 54 β Spiral Matrix.
Key Idea
The easiest way to think about spiral traversal is:
Repeatedly traverse the outermost layer, then move inward.
For every layer, there are at most four boundaries:
top
bottom
left
right
After traversing a boundary, move it inward.
top += 1
right -= 1
bottom -= 1
left += 1
This naturally leads to the four-pointer boundary traversal solution.
Approach 1 β Boundary Traversal with Four Pointers
This is the cleanest and most important interview approach.
Maintain:
top
bottom
left
right
representing the remaining unvisited rectangle.
Initially:
top = 0
bottom = m - 1
left = 0
right = n - 1
For every layer:
1. Traverse top row β left β right
2. Traverse right column β top β bottom
3. Traverse bottom row β right β left
4. Traverse left column β bottom β top
Then shrink the rectangle:
top += 1
right -= 1
bottom -= 1
left += 1
Intuition β The WHY
Consider:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13,14, 15,16]
]
Initially:
top = 0
bottom = 3
left = 0
right = 3
Traverse top
1 2 3 4
β β β β
Now the first row is finished:
top += 1
Traverse right
4
8
12
16
β
Now:
right -= 1
Traverse bottom
16 β 15 β 14 β 13
Now:
bottom -= 1
Traverse left
13
9
5
β
Now:
left += 1
The remaining rectangle is:
[
[6, 7],
[10, 11]
]
Repeat.
The key invariant is:
At the start of every iteration,
top...bottomandleft...rightrepresent exactly the unvisited portion of the matrix.
Python Solution
def spiralOrder(matrix):
if not matrix:
return []
result = []
top = 0
bottom = len(matrix) - 1
left = 0
right = len(matrix[0]) - 1
while top <= bottom and left <= right:
# 1. Traverse the top row: left -> right
for col in range(left, right + 1):
result.append(matrix[top][col])
# This row has now been completely processed.
top += 1
# 2. Traverse the right column: top -> bottom
for row in range(top, bottom + 1):
result.append(matrix[row][right])
# This column has now been completely processed.
right -= 1
# 3. Traverse the bottom row: right -> left
#
# The boundary checks are important because the matrix
# may have only one remaining row.
if top <= bottom:
for col in range(right, left - 1, -1):
result.append(matrix[bottom][col])
bottom -= 1
# 4. Traverse the left column: bottom -> top
#
# Again, check the remaining boundaries because the matrix
# may have only one remaining column.
if left <= right:
for row in range(bottom, top - 1, -1):
result.append(matrix[row][left])
left += 1
return result
Why Are the Two if Checks Necessary?
This is one of the most common spiral traversal bugs.
Suppose:
matrix =
[
[1, 2, 3]
]
After traversing the top row:
result = [1, 2, 3]
and:
top = 1
bottom = 0
There is no remaining bottom row.
Without:
if top <= bottom:
we might traverse an already-processed row again.
Similarly, for:
[
[1],
[2],
[3]
]
there is only one column. After traversing the right column, we must not traverse the left column again.
Hence:
if left <= right:
Dry Run
Consider:
[
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
Initial:
top = 0
bottom = 2
left = 0
right = 3
1. Top row
1 2 3 4
Result:
[1, 2, 3, 4]
Update:
top = 1
2. Right column
8
12
Result:
[1, 2, 3, 4, 8, 12]
Update:
right = 2
3. Bottom row
11 10 9
Result:
[1, 2, 3, 4, 8, 12, 11, 10, 9]
Update:
bottom = 1
4. Left column
5
Result:
[1, 2, 3, 4, 8, 12, 11, 10, 9, 5]
Update:
left = 1
Remaining rectangle:
[
[6, 7],
[10,11]
]
Repeat:
6 7 11 10
Final:
[1,2,3,4,8,12,11,10,9,5,6,7]
Approach 2 β Direction Simulation
A more literal way to solve spiral traversal is to simulate movement.
Maintain:
direction = right
and move:
right β down β left β up β right β ...
When the next cell is:
-
outside the matrix, or
-
already visited,
turn clockwise.
Python Solution
def spiralOrder(matrix):
if not matrix:
return []
m = len(matrix)
n = len(matrix[0])
visited = [[False] * n for _ in range(m)]
# Directions:
# right, down, left, up
directions = [
(0, 1),
(1, 0),
(0, -1),
(-1, 0)
]
result = []
row = col = 0
direction = 0
for _ in range(m * n):
result.append(matrix[row][col])
visited[row][col] = True
next_row = row + directions[direction][0]
next_col = col + directions[direction][1]
# If the next cell is outside the matrix or already visited,
# rotate clockwise.
if (
next_row < 0 or next_row >= m or
next_col < 0 or next_col >= n or
visited[next_row][next_col]
):
direction = (direction + 1) % 4
row += directions[direction][0]
col += directions[direction][1]
return result
Intuition
This approach directly models what you would do manually:
β β β β
β
β β β β
β
Once you hit a boundary or an already visited cell:
turn clockwise
It is conceptually simple because it is pure simulation.
Complexity
There are exactly:
mΓnm \times n
cells.
Each cell is visited once.
Therefore:
O(mn)\boxed{O(mn)}
time.
The visited matrix requires:
O(mn)O(mn)
auxiliary space.
The output itself also contains:
O(mn)O(mn)
elements.
Approach 3 β Layer-by-Layer Traversal
Another easy way to express the same idea is to explicitly iterate through layers.
For a matrix:
m Γ n
the number of complete layers is approximately:
βminβ‘(m,n)2β\left\lceil \frac{\min(m,n)}{2} \right\rceil
For every layer, calculate its:
top
bottom
left
right
and traverse its four sides.
This is conceptually the same algorithm as the four-boundary solution; the difference is mostly in how the boundaries are represented.
The four-pointer implementation is preferable because the shrinking boundaries naturally control termination and avoid separate layer calculations.
Comparing the Approaches
| Approach | Time | Auxiliary Space | Main Idea |
|---|---|---|---|
| Four-boundary pointers | Shrink remaining rectangle | ||
| Direction simulation | Move + turn when blocked | ||
| Layer-by-layer | Process each outer layer |
The boundary traversal is usually the best interview solution.
Important Variations
1. Spiral Traversal Starting From Another Direction
The same boundary idea can be adapted.
For example, starting from bottom-left and moving:
up β right β down β left
just changes the order in which boundaries are traversed and shrunk.
The underlying invariant remains:
Maintain the boundaries of the unvisited rectangle.
2. Generate a Matrix in Spiral Order
The reverse problem is common:
Given numbers from
1to , fill an matrix in spiral order.
The same four-boundary pattern is used, but instead of reading elements:
result.append(matrix[top][col])
you write values:
matrix[top][col] = value
This is a very direct transfer of the technique.
3. Spiral Matrix II β LC 59
LeetCode 59 β Spiral Matrix II asks you to create an matrix filled with 1...nΒ² in spiral order.
It is essentially the same boundary-traversal algorithm in reverse:
LC 54 β read matrix in spiral
LC 59 β write matrix in spiral
Common Mistakes / Quirks
Mistake 1 β Traversing an already-processed row or column
This is the biggest issue.
After:
top += 1
right -= 1
bottom -= 1
left += 1
the remaining boundaries must still be valid.
Thatβs why:
if top <= bottom:
and:
if left <= right:
are necessary.
Mistake 2 β Using the wrong starting point for the bottom row
After traversing the right column:
right -= 1
so the bottom row must traverse:
range(right, left - 1, -1)
not from the old right.
Otherwise, the bottom-right element gets processed twice.
Mistake 3 β Mixing boundary updates and traversal order
A useful mental model is:
TOP β traverse β shrink
RIGHT β traverse β shrink
BOTTOM β traverse β shrink
LEFT β traverse β shrink
Keeping that order consistent makes the implementation much easier to reason about.
Mistake 4 β Assuming the matrix is square
A matrix can be:
1 Γ n
m Γ 1
m Γ n
The algorithm must handle all three.
Do not write logic that assumes:
len(matrix) == len(matrix[0])
Pythonic Way
The direction-simulation solution can be made compact with zip, but that does not meaningfully improve the algorithm.
For interviews, the boundary version is both Pythonic enough and much clearer.
One useful Python detail is:
if not matrix:
return []
This handles an empty matrix before accessing:
matrix[0]
Complexity
For an matrix:
Time
Every element is processed exactly once:
O(mn)\boxed{O(mn)}
Auxiliary Space β Boundary Approach
Only four pointers and a few variables are maintained:
O(1)\boxed{O(1)}
excluding the output.
The output contains all elements:
O(mn)O(mn)
Therefore, including output:
O(mn)\boxed{O(mn)}
Direction-Simulation Approach
The visited matrix requires:
O(mn)O(mn)
auxiliary space, in addition to the output.
Key Takeaways / Pattern Recognition
The Core Pattern
When traversing a matrix in a spiral:
top
βββββββββββββββ
leftβ βright
β β
β β
βββββββββββββββ
bottom
maintain four boundaries:
top
bottom
left
right
and repeatedly:
β top row
β right column
β bottom row
β left column
then shrink:
top += 1
right -= 1
bottom -= 1
left += 1
Most Important Invariant
[top..bottom] Γ [left..right]is the unvisited region of the matrix.
This invariant is more important than memorizing the code.
If you can maintain that invariant, the implementation naturally follows.
Interview Preference
For spiral matrix problems, think in this order:
Need spiral traversal?
β
Can I represent the unvisited region
with four boundaries?
β
YES β top / bottom / left / right
β
Traverse four sides
β
Shrink boundaries
β
Check degenerate row/column cases
The direction-simulation method is useful to know because it is a general matrix simulation technique, but the four-pointer boundary approach is usually the cleaner and more space-efficient solution.
Memory hook:
Spiral Matrix = shrink a rectangle from four sides.