Courses › Coding Interviews › Patterns
Matrices
Rotation, spiral order, in-place state tricks and searching sorted matrices.
Matrix problems (rotate, spiral, set zeroes, game of life) rarely need a clever algorithm. They test careful index handling and, often, doing the work in place with O(1) extra space. A few reusable tricks cover nearly all of them.
Index basics
- An m × n grid:
rows, cols = len(grid), len(grid[0]). Check for empty input first. - Direction vectors keep neighbor loops short and bug-free:
DIRS4 = [(1, 0), (-1, 0), (0, 1), (0, -1)]
DIRS8 = DIRS4 + [(1, 1), (1, -1), (-1, 1), (-1, -1)]
def neighbors(r, c, rows, cols, dirs=DIRS4):
for dr, dc in dirs:
nr, nc = r + dr, c + dc
if 0 <= nr < rows and 0 <= nc < cols:
yield nr, nc- Flattening: cell
(r, c)↔ indexr * cols + c. This is useful for union-find, and for binary search over a sorted matrix whose rows continue one into the next. - Diagonals: every cell on one diagonal has the same
r - c, and every cell on one anti-diagonal has the samer + c. That's the key to diagonal traversal, Toeplitz checks and the N-Queens problem.
Rotate 90° clockwise in place
Rotation = transpose, then reverse each row. Two simple passes, instead of four-way swaps that are easy to get wrong.
def rotate(matrix):
n = len(matrix)
for i in range(n):
for j in range(i + 1, n): # transpose: swap across the diagonal
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
for row in matrix:
row.reverse() # counter-clockwise: reverse the columns insteadSpiral order: shrinking boundaries
Keep four boundaries and shrink them after walking each side. Re-check the boundaries before the bottom and left sides, so single rows and single columns aren't counted twice.
def spiral_order(matrix):
out = []
top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
out += [matrix[top][c] for c in range(left, right + 1)]
top += 1
out += [matrix[r][right] for r in range(top, bottom + 1)]
right -= 1
if top <= bottom:
out += [matrix[bottom][c] for c in range(right, left - 1, -1)]
bottom -= 1
if left <= right:
out += [matrix[r][left] for r in range(bottom, top - 1, -1)]
left += 1
return outEncoding extra state in place
When you have to update cells based on their old values without a copy:
- Set matrix zeroes: use the first row and first column as markers for which rows and columns to clear, plus two flags for whether the first row or column itself contained a zero.
- Game of life: encode both the old and new state in each cell, for example with bits. Bit 0 is the current state, bit 1 the next state. Shift everything right at the end.
def game_of_life(board):
rows, cols = len(board), len(board[0])
for r in range(rows):
for c in range(cols):
live = sum(board[nr][nc] & 1 for nr, nc in neighbors(r, c, rows, cols, DIRS8))
if board[r][c] & 1 and live in (2, 3) or not board[r][c] & 1 and live == 3:
board[r][c] |= 2 # alive in the next generation
for r in range(rows):
for c in range(cols):
board[r][c] >>= 1Searching sorted matrices
In-place vs. a copy
Always ask: "Can I modify the input?" A copy makes the code simpler and is often fine. If the interviewer wants O(1) extra space, that's when the encoding tricks above come in.
[!WARNING] Rows vs. columns Most matrix bugs come from swapping rows and cols in a non-square grid. Test with a 2 × 3 matrix, not only a square one.
[!TIP] Practice Rotate Image · Spiral Matrix · Spiral Matrix II · Set Matrix Zeroes · Game of Life · Diagonal Traverse · Toeplitz Matrix · Valid Sudoku · Search a 2D Matrix II · Kth Smallest in a Sorted Matrix