Rotate Image
A clockwise turn is a transpose followed by reversing each row, and both steps swap cells in place. Solved two ways, copying into a new grid with O(n²) extra space and transpose then reverse with O(1) extra space, both O(n²) time, with a step-through, code in five languages and the Big O worked out from the code.
The problem
Section titled “The problem”You are given an n × n grid of numbers, matrix. The code calls n size. Turn it 90 degrees clockwise. Change matrix
itself: do not build and return a second grid.
Example 1
Input: matrix = [[10, 11, 12, 13], [14, 15, 16, 17], [18, 19, 20, 21], [22, 23, 24, 25]]
Output: [[22, 18, 14, 10], [23, 19, 15, 11], [24, 20, 16, 12], [25, 21, 17, 13]]
The top row, 10 to 13, is now the right column, read top to bottom.
| c0 | c1 | c2 | c3 | |
|---|---|---|---|---|
| r0 | 22 | 18 | 14 | 10 |
| r1 | 23 | 19 | 15 | 11 |
| r2 | 24 | 20 | 16 | 12 |
| r3 | 25 | 21 | 17 | 13 |
Example 2
Input: matrix = [[3, 7, 1], [9, 5, 2], [4, 8, 6]]
Output: [[4, 9, 3], [8, 5, 7], [6, 2, 1]]
The left column, read bottom to top, becomes the top row: 4, 9, 3.
Example 3
Input: matrix = [[6]]
Output: [[6]]
A single cell looks the same after any turn.
Constraints
Section titled “Constraints”- 1 ≤ n ≤ 20
- −1,000 ≤ matrix[i][j] ≤ 1,000
- The grid is square: n rows and n columns.
Clarifying questions
Section titled “Clarifying questions”- Clockwise or counterclockwise?
- Clockwise. The top row becomes the right column.
- Must it be in place?
- Yes. The grid itself must change, so the best answer uses no second grid.
- Is the grid always square?
- Yes, n × n. A turn of a non-square grid would change its shape.
- Where does matrix[i][j] go?
- To row j, column n − 1 − i.
Spot the pattern
Section titled “Spot the pattern”Rotation looks like a hard move, but it breaks into two easy ones. A transpose flips the grid across its main diagonal, so rows become columns. Reversing each row then mirrors it left to right. Both are plain swaps of two cells, so they need no extra grid.
Solutions
Section titled “Solutions”Rotating four cells at a time, ring by ring, also works in place with the same Big O. Transpose and reverse is easier to get right.
Copy to a new grid O(n²), O(n²) space
The idea
Section titled “The idea”Make a new empty grid rotated. Each value matrix[i][j] belongs at rotated[j][size − 1 − i]: row 0 becomes
the last column, row 1 the column before it, and so on. Then copy rotated back into matrix.
It is easy to get right. But the problem asks for an in-place change, and rotated is a whole second grid.
Walkthrough
Section titled “Walkthrough”| c0 | c1 | c2 | c3 | |
|---|---|---|---|---|
| r0 | · | · | · | · |
| r1 | · | · | · | · |
| r2 | · | · | · | · |
| r3 | · | · | · | · |
- rotated = [[0] * size for _ in range(size)]
- for i in range(size):
- for j in range(size):
- rotated[j][size - 1 - i] = matrix[i][j]
- matrix[:] = rotated # copy the rows back
Make a new empty grid, rotated. Each value in matrix row i goes to column size − 1 − i of rotated.
class Solution { // Copy to a new grid: value [i][j] lands at [j][size - 1 - i]. Then copy back. // O(n^2) time, and the second grid costs O(n^2) memory. public void rotate(int[][] matrix) { int size = matrix.length; int[][] rotated = new int[size][size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { // Row i becomes column size - 1 - i, read top to bottom. rotated[j][size - 1 - i] = matrix[i][j]; } } // The caller holds matrix, so change its rows, not just our local name. for (int i = 0; i < size; i++) matrix[i] = rotated[i]; }}# Copy to a new grid: value [i][j] lands at [j][size - 1 - i]. Then copy back.# O(n^2) time, and the second grid costs O(n^2) memory.def rotate(matrix: list[list[int]]) -> None: size = len(matrix) rotated = [[0] * size for _ in range(size)] for i in range(size): for j in range(size): # Row i becomes column size - 1 - i, read top to bottom. rotated[j][size - 1 - i] = matrix[i][j] # The caller holds matrix, so change it, not just our local name. matrix[:] = rotatedpublic class Solution{ // Copy to a new grid: value [i][j] lands at [j][size - 1 - i]. Then copy back. // O(n^2) time, and the second grid costs O(n^2) memory. public void Rotate(int[][] matrix) { int size = matrix.Length; var rotated = new int[size][]; for (int i = 0; i < size; i++) rotated[i] = new int[size]; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { // Row i becomes column size - 1 - i, read top to bottom. rotated[j][size - 1 - i] = matrix[i][j]; } } // The caller holds matrix, so change its rows, not just our local name. for (int i = 0; i < size; i++) matrix[i] = rotated[i]; }}// Copy to a new grid: value [i][j] lands at [j][size - 1 - i]. Then copy back.// O(n^2) time, and the second grid costs O(n^2) memory.func rotate(matrix [][]int) { size := len(matrix) rotated := make([][]int, size) for i := range rotated { rotated[i] = make([]int, size) } for i := 0; i < size; i++ { for j := 0; j < size; j++ { // Row i becomes column size - 1 - i, read top to bottom. rotated[j][size-1-i] = matrix[i][j] } } // The caller holds matrix, so change its rows, not just our local name. copy(matrix, rotated)}// Copy to a new grid: value [i][j] lands at [j][size - 1 - i]. Then copy back.// O(n^2) time, and the second grid costs O(n^2) memory.function rotate(matrix: number[][]): void { const size = matrix.length; const rotated = Array.from({ length: size }, () => new Array<number>(size).fill(0)); for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { // Row i becomes column size - 1 - i, read top to bottom. rotated[j][size - 1 - i] = matrix[i][j]; } } // The caller holds matrix, so change its rows, not just our local name. for (let i = 0; i < size; i++) matrix[i] = rotated[i];}1 Find the work that repeats
The lit lines place one value, then put the new rows back. Placing sits inside two loops, one over rows and one over columns. Copying one value is 1 step.
- rotated = [[0] * size for _ in range(size)]
- for i in range(size):
- for j in range(size):
- rotated[j][size - 1 - i] = matrix[i][j]
- matrix[:] = rotated # copy the rows back
2 Count the runs on Example 1
Example 1 is 4 × 4, so 16 values. Placing them takes 16 runs. Putting the 4 new rows back into
matrixtakes 4 more.16 + 4 = 20 steps.
part place cells copy rows back steps 16 4 total16 + 4 = 203 Write the count for any n
n² (place) + n (rows back) = n² + nCheck it with Example 1: 16 + 4 = 20.
4 Keep only the part that grows fastest
Drop the + n. Next to n² it soon stops mattering. What is left is n². Note that n² is also the size of the input, so no answer can be faster: every value must move.
So the time is O(n²).
S Space: count the extra memory the same way
rotatedholds n × n values, a full second grid. The space is O(n²).rotated (n × n grid)ij
Transpose, then reverse O(n²), O(1) space
The idea
Section titled “The idea”Two moves in a row give a clockwise turn.
- Transpose: swap
matrix[i][j]withmatrix[j][i]for every cell above the diagonal. Now row i holds column i of the original, read top to bottom. - Reverse each row. A clockwise turn needs each original column read bottom to top, so flip every row.
Why it is correct: the transpose sends [i][j] to [j][i]. Reversing row j then sends column i
to column n − 1 − i. Together, [i][j] ends at [j][size − 1 − i], which is exactly a clockwise turn.
Walkthrough
Section titled “Walkthrough”| c0 | c1 | c2 | c3 | |
|---|---|---|---|---|
| r0 | 10 | 11 | 12 | 13 |
| r1 | 14 | 15 | 16 | 17 |
| r2 | 18 | 19 | 20 | 21 |
| r3 | 22 | 23 | 24 | 25 |
- for i in range(size):
- for j in range(i + 1, size):
- matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
- for row in matrix:
- row.reverse()
Two moves give a clockwise turn: transpose (flip across the main diagonal), then reverse each row. Both work in place.
class Solution { // Transpose, then reverse each row: together they send [i][j] to [j][size - 1 - i]. // Every cell moves once: O(n^2) time and O(1) extra memory. public void rotate(int[][] matrix) { int size = matrix.length; // j starts above the diagonal, so each pair is swapped once, not twice. for (int i = 0; i < size; i++) { for (int j = i + 1; j < size; j++) { int temporary = matrix[i][j]; matrix[i][j] = matrix[j][i]; matrix[j][i] = temporary; } } // Row i now holds column i top to bottom. Clockwise needs it bottom to top. for (int[] row : matrix) { for (int left = 0, right = size - 1; left < right; left++, right--) { int temporary = row[left]; row[left] = row[right]; row[right] = temporary; } } }}# Transpose, then reverse each row: together they send [i][j] to [j][size - 1 - i].# Every cell moves once: O(n^2) time and O(1) extra memory.def rotate(matrix: list[list[int]]) -> None: size = len(matrix) # j starts above the diagonal, so each pair is swapped once, not twice. for i in range(size): for j in range(i + 1, size): matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] # Row i now holds column i top to bottom. Clockwise needs it bottom to top. for row in matrix: row.reverse()public class Solution{ // Transpose, then reverse each row: together they send [i][j] to [j][size - 1 - i]. // Every cell moves once: O(n^2) time and O(1) extra memory. public void Rotate(int[][] matrix) { int size = matrix.Length; // j starts above the diagonal, so each pair is swapped once, not twice. for (int i = 0; i < size; i++) { for (int j = i + 1; j < size; j++) { (matrix[i][j], matrix[j][i]) = (matrix[j][i], matrix[i][j]); } } // Row i now holds column i top to bottom. Clockwise needs it bottom to top. foreach (var row in matrix) System.Array.Reverse(row); }}// Transpose, then reverse each row: together they send [i][j] to [j][size - 1 - i].// Every cell moves once: O(n^2) time and O(1) extra memory.func rotate(matrix [][]int) { size := len(matrix) // j starts above the diagonal, so each pair is swapped once, not twice. for i := 0; i < size; i++ { for j := i + 1; j < size; j++ { matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j] } } // Row i now holds column i top to bottom. Clockwise needs it bottom to top. for _, row := range matrix { slices.Reverse(row) }}// Transpose, then reverse each row: together they send [i][j] to [j][size - 1 - i].// Every cell moves once: O(n^2) time and O(1) extra memory.function rotate(matrix: number[][]): void { const size = matrix.length; // j starts above the diagonal, so each pair is swapped once, not twice. for (let i = 0; i < size; i++) { for (let j = i + 1; j < size; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // Row i now holds column i top to bottom. Clockwise needs it bottom to top. for (const row of matrix) row.reverse();}1 Find the work that repeats
Two parts repeat. The transpose swaps one pair per run, inside two loops. The reverse swaps pairs inside each row. One swap is 1 step.
- for i in range(size):
- for j in range(i + 1, size):
- matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
- for row in matrix:
- row.reverse()
2 Count the runs on Example 1
Example 1 is 4 × 4. The transpose swaps each pair above the diagonal once: 6 swaps. Each of the 4 rows takes 2 swaps to reverse: 8 swaps.
6 + 8 = 14 swaps.
part transpose reverse rows swaps 6 8 total6 + 8 = 143 Write the count for any n
Above the diagonal there are n × (n − 1) / 2 cells. Each row reverse takes n / 2 swaps:
n(n − 1)/2 + n × n/2 = n² − n/2Check it with Example 1: 16 − 2 = 14.
4 Keep only the part that grows fastest
Drop the − n/2. What is left is n², the same as the copy, but with no second grid.
So the time is O(n²).
S Space: count the extra memory the same way
The code keeps only
iandj, and the swaps use one temporary value. The space is O(1).ij
Tested at the limits
Section titled “Tested at the limits”2,000 random grids from 1 x 1 to 6 x 6, odd and even sizes, with many repeated values. Copy to a new grid and transpose, then reverse gave the same answer every time.
| Largest inputs | Result |
|---|---|
| A 20 x 20 grid (the most allowed), every value different. Transpose, then reverse. | Under 0.001 s, and under 0.001 MB of extra memory: swaps in place, no new rows. |
| The same grid. Copy to a new grid. | Under 0.001 s, and about 0.005 MB: a full second grid of 400 values. |
The designbeyondcode digest, every Saturday
New deep dives, interview tips and site news in one short email. Free. Leave with one click.
We never sell or share your email. We only use it to send this newsletter.