LeetCode #48 Medium

Rotate Image

Rotate an n × n matrix 90° clockwise in place (no extra matrix).

arraymatrixmath
Open on LeetCode ↗
02

Intuition

A 90° clockwise rotation equals two simple in-place moves: transpose the matrix (swap across the main diagonal), then reverse each row. Together they send every element to its rotated position.

How to spot this pattern

Transpose, then reverse each row — two simple passes replacing one confusing four-way cycle. Recognising that a rotation decomposes into a reflection across the diagonal followed by a horizontal flip is the trick worth keeping; the anticlockwise rotation is the same pair with the reverse applied to columns instead.

03

Approach

1

Copying into a new matrix is the easy way out

You can allocate a fresh n×n grid and place each element at its rotated coordinate using a formula. That's straightforward but uses O(n²) extra space, which the in-place requirement forbids. The insight is that a rotation can be decomposed into two simpler in-place operations that compose to the same result.

2

A 90° rotation = transpose + reverse rows

Transposing the matrix swaps matrix[i][j] with matrix[j][i], turning rows into columns — this gets every element onto the correct line but mirrored. Reversing each row afterward flips that mirror, landing every element in its final rotated position. It's worth convincing yourself on a small example: together these two flips equal a clockwise quarter-turn.

3

Transpose carefully, then reverse

Do the transpose with the inner loop starting at j = i + 1, so each off-diagonal pair is swapped exactly once — swapping the full grid would undo itself. Then reverse each row in place. Both steps touch each cell a constant number of times: O(n²) time, O(1) extra space. A 1×1 grid is a no-op, as a rotation should be.

04

Solution & live demo

1class Solution:
2 def rotate(self, matrix):
3 n = len(matrix)
4 for i in range(n):
5 for j in range(i + 1, n):
6 matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
7 for row in matrix:
8 row.reverse()
05

Common pitfalls

Transposing over the full index range

✗ Wrong
for i in range(n):
    for j in range(n):
        matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
✓ Right
for i in range(n):
    for j in range(i + 1, n):

Every pair is swapped twice — once as (i, j) and once as (j, i) — which returns the matrix to its original state. Iterating only above the diagonal touches each pair exactly once.

Reversing before transposing

✗ Wrong
for row in matrix: row.reverse()
# then transpose
✓ Right
# transpose
for row in matrix: row.reverse()

The two operations don't commute — reversing first produces a 90° anticlockwise rotation. Order is the entire difference between the two directions.

Building a new matrix

✗ Wrong
return [list(row) for row in zip(*matrix[::-1])]
✓ Right
# swap in place, then reverse rows

Elegant, but the problem requires modifying the input in place with O(1) extra space. Returning a new grid also leaves the caller's matrix unchanged, so the expected output never appears.

06

Edge cases

1×1 matrix

Transpose and row-reverse are both no-ops; the single cell is unchanged, as a rotation should leave it.

2×2 matrix

One diagonal swap plus two row reversals produce the correct 90° turn.

Double-swap avoidance

Restricting the transpose to j > i ensures each off-diagonal pair is swapped exactly once.

07

Complexity

Time
O(n²)
Space
O(1)
Every cell is touched a constant number of times, in place.