Spiral Matrix
Given an m x n matrix, return all elements of the matrix in spiral order.
- m == matrix.length
- n == matrix[i].length
- 1 <= m, n <= 10
- -100 <= matrix[i][j] <= 100
matrix = [[1,2,3],[4,5,6],[7,8,9]][1,2,3,6,9,8,7,4,5]matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]][1,2,3,4,8,12,11,10,9,5,6,7]matrix = [[7],[9],[6]][7,9,6]Spiral order reads the grid the way you would peel it: start at the top-left, walk right along the top edge, down the right edge, left along the bottom edge, up the left edge — then repeat the same clockwise loop one layer further in, until every cell is collected. Two things make it harder than it looks: knowing exactly when to turn, and never visiting a cell twice — especially in rectangular (non-square) grids, where the last surviving layer may be a single row or single column.
The most direct translation: keep a current direction (right, down, left, up as coordinate deltas), step forward while the next cell is inside the grid and unvisited, otherwise turn clockwise. A set remembers visited cells.
res = []
visited = set()
r, c = 0, 0
dr, dc = [0, 1, 0, -1], [1, 0, -1, 0] # right, down, left, up
di = 0 # index of the current direction
for _ in range(rows * cols):
res.append(matrix[r][c])
visited.add((r, c))
nr, nc = r + dr[di], c + dc[di]
if 0 <= nr < rows and 0 <= nc < cols and (nr, nc) not in visited:
r, c = nr, nc
else:
di = (di + 1) % 4 # blocked: turn clockwise once
r, c = r + dr[di], c + dc[di]This works, and the turning rule is honest: in a spiral you turn exactly when the cell ahead is either off the grid or already taken. The cost of knowing "already taken" is the visited set — O(M×N) extra memory on top of the walk.
Watch what each straight run consumes: the first run eats the entire top row; the next eats the entire remaining right column; then the remaining bottom row; then the remaining left column. After every run, one full row or column is permanently spent. Which means the unvisited region is always a clean rectangle — and a rectangle is fully described by four numbers: top, bottom, left, right. Instead of remembering M×N individual cells, maintain the invariant "everything inside the four fences is unvisited": walk an edge of the rectangle, then pull that fence inward by one. Turning happens exactly at the fences; revisiting is impossible because visited territory is, by construction, outside them.
res = []
top, bottom = 0, rows - 1
left, right = 0, cols - 1
while len(res) < rows * cols:
for j in range(left, right + 1): res.append(matrix[top][j])
top += 1 # top row spent
if len(res) == rows * cols: break
for i in range(top, bottom + 1): res.append(matrix[i][right])
right -= 1 # right column spent
if len(res) == rows * cols: break
for j in range(right, left - 1, -1): res.append(matrix[bottom][j])
bottom -= 1 # bottom row spent
if len(res) == rows * cols: break
for i in range(bottom, top - 1, -1): res.append(matrix[i][left])
left += 1 # left column spent
return resif len(res) == rows cols: break guard after every edge is not paranoia — it is where rectangular grids are won or lost. Suppose the surviving region is a single row: the top-edge walk consumes it entirely. Without the guard, the loop would continue to the bottom-edge walk — and bottom still points at that same* row, so it would be re-walked in reverse, emitting duplicates. Checking the count after each edge stops the machine the instant the last cell is collected.O(M×N) time — optimal, since every element must be output — and O(1) extra space beyond the answer. The generalizable move: replace per-cell bookkeeping with a shape invariant. We never tracked which cells were visited; we maintained a guarantee ("the unvisited region is the rectangle inside the fences") that four integers could carry. Whenever per-item state feels unavoidable, ask whether the processed region has a simple describable shape — if it does, a handful of boundary variables can replace an entire data structure.
Spiral Traversal
Starting at [0,0]. Moving Right along the Top Wall.
Shrinking Bounds
We track four walls: top, bottom, left, and right. Every time we finish traversing a wall, we shrink it inward, ensuring we never visit the same cell twice.
Edge Cases
Crucial: After moving left or up, check if the boundaries have crossed (top <= bottom) to avoid double-processing elements in non-square matrices.