Coding patternsArrays and strings
Matrices
The matrix (2D grid) coding pattern for interviews. Walking a grid, layers and spirals, rotating and marking cells in place, with a step-through, templates in Java, Python, C#, Go and TypeScript, and 10 LeetCode problems.
- Read first
- Two pointers
A matrix is a grid of values: m rows, n columns, and a cell at grid[row][column]. Images, game
boards, maps and spreadsheets are all matrices. Matrix problems rarely need a clever algorithm. They
need careful index work: which way you walk, where you stop, and how you avoid reading a cell
you already changed.
This page covers the three moves that most matrix questions use. Walk the grid with clear bounds. Peel it in layers, the rings from the outside in. And mark cells in place, so you don’t need a second grid. When a grid question is really “which cells can reach which”, it’s a graph. Then the answer is on the graph search page.
How to spot it
Section titled “How to spot it”- The input is a 2D array: a board, an image, a map or a table.
- “Rotate”, “transpose”, “spiral”, “diagonal”. These are walks in a fixed order, and the work is in the indexes.
- “In place” or “O(1) extra memory” on a grid. You’ll need to keep notes inside the grid.
- Every cell’s new value depends on its neighbors’ old values, as in Game of Life. You need to keep the old values readable while you write new ones.
- A sorted grid. Rows and columns sorted means you can rule out a whole row or column with one comparison.
Watch it run
Section titled “Watch it run”Read a 4 × 4 grid in spiral order. The four walls fence in the cells not yet taken. Watch how each side is taken and then its wall moves in by one. When the walls cross, the grid is done.
| row \ column | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| 0 | 1 | 2 | 3 | 4 |
| 1 | 5 | 6 | 7 | 8 |
| 2 | 9 | 10 | 11 | 12 |
| 3 | 13 | 14 | 15 | 16 |
- top, bottom = 0, len(matrix) - 1
- left, right = 0, len(matrix[0]) - 1
- while top <= bottom and left <= right:
- take top row →; top += 1
- take right column ↓; right -= 1
- if top <= bottom:
- take bottom row ←; bottom -= 1
- if left <= right:
- take left column ↑; left += 1
Four walls fence in the cells not yet taken. The highlighted block is everything still inside them.
The walls make the loop safe without a visited grid. A cell outside the walls is always taken, and
a cell inside is never taken. There’s one trap. After the top row and the right column are taken,
only one row or one column may be left. The checks if top <= bottom and if left <= right stop
the loop from taking that last line twice, once in each direction.
The template
Section titled “The template”Spiral order with walls, rotation in place, and setting zeroes with the first row and column as markers:
import java.util.ArrayList;import java.util.Arrays;import java.util.List;
public class Matrices { static List<Integer> spiralOrder(int[][] matrix) { // Spiral order with four walls around the part not yet taken. Take one side, then move that // wall inward. Every cell is taken once: O(m·n) time for m rows and n columns, O(1) extra // memory beyond the output. List<Integer> result = new ArrayList<>(); int top = 0, bottom = matrix.length - 1, left = 0, right = matrix[0].length - 1; while (top <= bottom && left <= right) { for (int column = left; column <= right; column++) { result.add(matrix[top][column]); } top++; for (int row = top; row <= bottom; row++) { result.add(matrix[row][right]); } right--; // The checks stop a single leftover row or column from being taken twice. if (top <= bottom) { for (int column = right; column >= left; column--) { result.add(matrix[bottom][column]); } bottom--; } if (left <= right) { for (int row = bottom; row >= top; row--) { result.add(matrix[row][left]); } left++; } } return result; }
static void rotate(int[][] matrix) { // Rotate a square grid 90° clockwise in place. A transpose (swap across the main // diagonal) followed by reversing each row moves every cell to its rotated spot. // O(n²) time for an n × n grid, O(1) memory. int size = matrix.length; for (int row = 0; row < size; row++) { // column > row, so each pair swaps once, not twice. for (int column = row + 1; column < size; column++) { int saved = matrix[row][column]; matrix[row][column] = matrix[column][row]; matrix[column][row] = saved; } } for (int[] row : matrix) { for (int i = 0, j = size - 1; i < j; i++, j--) { int saved = row[i]; row[i] = row[j]; row[j] = saved; } } }
static void setZeroes(int[][] matrix) { // Set a cell's whole row and column to 0 if it holds 0. The first row and first column // store the marks, so no extra grid is needed. Their own zeros are remembered in two flags // first, because the marks would hide them. O(m·n) time for m rows and n columns, O(1) // memory. int rowCount = matrix.length, columnCount = matrix[0].length; boolean firstRow = false, firstColumn = false; for (int column = 0; column < columnCount; column++) firstRow |= matrix[0][column] == 0; for (int row = 0; row < rowCount; row++) firstColumn |= matrix[row][0] == 0; for (int row = 1; row < rowCount; row++) { for (int column = 1; column < columnCount; column++) { // Mark row and column. if (matrix[row][column] == 0) matrix[row][0] = matrix[0][column] = 0; } } for (int row = 1; row < rowCount; row++) { for (int column = 1; column < columnCount; column++) { if (matrix[row][0] == 0 || matrix[0][column] == 0) matrix[row][column] = 0; } } // Clear the marker row and column last, so the marks are read before they change. if (firstRow) Arrays.fill(matrix[0], 0); if (firstColumn) for (int row = 0; row < rowCount; row++) matrix[row][0] = 0; }
public static void main(String[] args) { check(spiralOrder(new int[][] {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}) .equals(List.of(1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7))); check(spiralOrder(new int[][] {{1, 2}, {3, 4}}).equals(List.of(1, 2, 4, 3))); check(spiralOrder(new int[][] {{5}, {6}, {7}}).equals(List.of(5, 6, 7))); check(spiralOrder(new int[][] {{9}}).equals(List.of(9))); int[][] grid = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; rotate(grid); check(Arrays.deepEquals(grid, new int[][] {{7, 4, 1}, {8, 5, 2}, {9, 6, 3}})); grid = new int[][] {{1, 1, 1}, {1, 0, 1}, {1, 1, 1}}; setZeroes(grid); check(Arrays.deepEquals(grid, new int[][] {{1, 0, 1}, {0, 0, 0}, {1, 0, 1}})); grid = new int[][] {{0, 1, 2, 0}, {3, 4, 5, 2}, {1, 3, 1, 5}}; setZeroes(grid); check(Arrays.deepEquals(grid, new int[][] {{0, 0, 0, 0}, {0, 4, 5, 0}, {0, 3, 1, 0}})); System.out.println("ok: spiralOrder, rotate and setZeroes"); }
static void check(boolean ok) { if (!ok) throw new AssertionError(); }}# Spiral order with four walls around the part not yet taken. Take one side, then move that wall# inward. Every cell is taken once: O(m·n) time for m rows and n columns, O(1) extra memory beyond# the output.def spiral_order(matrix: list[list[int]]) -> list[int]: result: list[int] = [] top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1 while top <= bottom and left <= right: for column in range(left, right + 1): result.append(matrix[top][column]) top += 1 for row in range(top, bottom + 1): result.append(matrix[row][right]) right -= 1 # The checks stop a single leftover row or column from being taken twice. if top <= bottom: for column in range(right, left - 1, -1): result.append(matrix[bottom][column]) bottom -= 1 if left <= right: for row in range(bottom, top - 1, -1): result.append(matrix[row][left]) left += 1 return result
# Rotate a square grid 90° clockwise in place. A transpose (swap across the main diagonal) followed# by reversing each row moves every cell to its rotated spot. O(n²) time for an n × n grid, O(1)# memory.def rotate(matrix: list[list[int]]) -> None: size = len(matrix) for row in range(size): for column in range(row + 1, size): # column > row, so each pair swaps once, not twice matrix[row][column], matrix[column][row] = matrix[column][row], matrix[row][column] for row in matrix: row.reverse()
# Set a cell's whole row and column to 0 if it holds 0. The first row and first column store# the marks, so no extra grid is needed. Their own zeros are remembered in two flags first,# because the marks would hide them. O(m·n) time for m rows and n columns, O(1) memory.def set_zeroes(matrix: list[list[int]]) -> None: row_count, column_count = len(matrix), len(matrix[0]) first_row = any(matrix[0][column] == 0 for column in range(column_count)) first_column = any(matrix[row][0] == 0 for row in range(row_count)) for row in range(1, row_count): for column in range(1, column_count): if matrix[row][column] == 0: matrix[row][0] = matrix[0][column] = 0 # mark the row and the column for row in range(1, row_count): for column in range(1, column_count): if matrix[row][0] == 0 or matrix[0][column] == 0: matrix[row][column] = 0 # Clear the marker row and column last, so the marks are read before they change. if first_row: for column in range(column_count): matrix[0][column] = 0 if first_column: for row in range(row_count): matrix[row][0] = 0
wide = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]assert spiral_order(wide) == [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]assert spiral_order([[1, 2], [3, 4]]) == [1, 2, 4, 3]assert spiral_order([[5], [6], [7]]) == [5, 6, 7]assert spiral_order([[9]]) == [9]grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]rotate(grid)assert grid == [[7, 4, 1], [8, 5, 2], [9, 6, 3]]grid = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]set_zeroes(grid)assert grid == [[1, 0, 1], [0, 0, 0], [1, 0, 1]]grid = [[0, 1, 2, 0], [3, 4, 5, 2], [1, 3, 1, 5]]set_zeroes(grid)assert grid == [[0, 0, 0, 0], [0, 4, 5, 0], [0, 3, 1, 0]]print("ok: spiral_order, rotate and set_zeroes")using System;using System.Collections.Generic;using System.Linq;
static class Matrices{ public static List<int> SpiralOrder(int[][] matrix) { // Spiral order with four walls around the part not yet taken. Take one side, then move that // wall inward. Every cell is taken once: O(m·n) time for m rows and n columns, O(1) extra // memory beyond the output. var output = new List<int>(); int top = 0, bottom = matrix.Length - 1, left = 0, right = matrix[0].Length - 1; while (top <= bottom && left <= right) { for (int column = left; column <= right; column++) { output.Add(matrix[top][column]); } top++; for (int row = top; row <= bottom; row++) { output.Add(matrix[row][right]); } right--; // The checks stop a single leftover row or column from being taken twice. if (top <= bottom) { for (int column = right; column >= left; column--) { output.Add(matrix[bottom][column]); } bottom--; } if (left <= right) { for (int row = bottom; row >= top; row--) { output.Add(matrix[row][left]); } left++; } } return output; }
public static void Rotate(int[][] matrix) { // Rotate a square grid 90° clockwise in place. A transpose (swap across the main // diagonal) followed by reversing each row moves every cell to its rotated spot. // O(n²) time for an n × n grid, O(1) memory. int size = matrix.Length; for (int row = 0; row < size; row++) { // column > row, so each pair swaps once, not twice. for (int column = row + 1; column < size; column++) { (matrix[row][column], matrix[column][row]) = (matrix[column][row], matrix[row][column]); } } foreach (int[] row in matrix) Array.Reverse(row); }
public static void SetZeroes(int[][] matrix) { // Set a cell's whole row and column to 0 if it holds 0. The first row and first column // store the marks, so no extra grid is needed. Their own zeros are remembered in two flags // first, because the marks would hide them. O(m·n) time for m rows and n columns, O(1) // memory. int rowCount = matrix.Length, columnCount = matrix[0].Length; bool firstRow = matrix[0].Contains(0); bool firstColumn = matrix.Any(row => row[0] == 0); for (int row = 1; row < rowCount; row++) { for (int column = 1; column < columnCount; column++) { // Mark row and column. if (matrix[row][column] == 0) matrix[row][0] = matrix[0][column] = 0; } } for (int row = 1; row < rowCount; row++) { for (int column = 1; column < columnCount; column++) { if (matrix[row][0] == 0 || matrix[0][column] == 0) matrix[row][column] = 0; } } // Clear the marker row and column last, so the marks are read before they change. if (firstRow) Array.Fill(matrix[0], 0); if (firstColumn) foreach (int[] row in matrix) row[0] = 0; }
static void Main() { Check(SpiralOrder([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]) .SequenceEqual(new[] { 1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7 })); Check(SpiralOrder([[1, 2], [3, 4]]).SequenceEqual(new[] { 1, 2, 4, 3 })); Check(SpiralOrder([[5], [6], [7]]).SequenceEqual(new[] { 5, 6, 7 })); Check(SpiralOrder([[9]]).SequenceEqual(new[] { 9 })); int[][] grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; Rotate(grid); Check(Same(grid, [[7, 4, 1], [8, 5, 2], [9, 6, 3]])); grid = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]; SetZeroes(grid); Check(Same(grid, [[1, 0, 1], [0, 0, 0], [1, 0, 1]])); grid = [[0, 1, 2, 0], [3, 4, 5, 2], [1, 3, 1, 5]]; SetZeroes(grid); Check(Same(grid, [[0, 0, 0, 0], [0, 4, 5, 0], [0, 3, 1, 0]])); Console.WriteLine("ok: SpiralOrder, Rotate and SetZeroes"); }
static bool Same(int[][] a, int[][] b) => a.Length == b.Length && a.Zip(b).All(p => p.First.SequenceEqual(p.Second));
static void Check(bool ok) { if (!ok) throw new InvalidOperationException("check failed"); }}package main
import ( "fmt" "reflect" "slices")
// Spiral order with four walls around the part not yet taken. Take one side, then move that wall// inward. Every cell is taken once: O(m·n) time for m rows and n columns, O(1) extra memory beyond// the output.func spiralOrder(matrix [][]int) []int { var result []int top, bottom, left, right := 0, len(matrix)-1, 0, len(matrix[0])-1 for top <= bottom && left <= right { for column := left; column <= right; column++ { result = append(result, matrix[top][column]) } top++ for row := top; row <= bottom; row++ { result = append(result, matrix[row][right]) } right-- // The checks stop a single leftover row or column from being taken twice. if top <= bottom { for column := right; column >= left; column-- { result = append(result, matrix[bottom][column]) } bottom-- } if left <= right { for row := bottom; row >= top; row-- { result = append(result, matrix[row][left]) } left++ } } return result}
// Rotate a square grid 90° clockwise in place. A transpose (swap across the main diagonal) followed// by reversing each row moves every cell to its rotated spot. O(n²) time for an n × n grid, O(1)// memory.func rotate(matrix [][]int) { size := len(matrix) for row := 0; row < size; row++ { // column > row, so each pair swaps once, not twice. for column := row + 1; column < size; column++ { matrix[row][column], matrix[column][row] = matrix[column][row], matrix[row][column] } } for _, row := range matrix { slices.Reverse(row) }}
// Set a cell's whole row and column to 0 if it holds 0. The first row and first column store// the marks, so no extra grid is needed. Their own zeros are remembered in two flags first,// because the marks would hide them. O(m·n) time for m rows and n columns, O(1) memory.func setZeroes(matrix [][]int) { rowCount, columnCount := len(matrix), len(matrix[0]) firstRow := slices.Contains(matrix[0], 0) firstColumn := false for row := 0; row < rowCount; row++ { firstColumn = firstColumn || matrix[row][0] == 0 } for row := 1; row < rowCount; row++ { for column := 1; column < columnCount; column++ { if matrix[row][column] == 0 { matrix[row][0], matrix[0][column] = 0, 0 // mark the row and the column } } } for row := 1; row < rowCount; row++ { for column := 1; column < columnCount; column++ { if matrix[row][0] == 0 || matrix[0][column] == 0 { matrix[row][column] = 0 } } } // Clear the marker row and column last, so the marks are read before they change. if firstRow { clear(matrix[0]) } if firstColumn { for row := 0; row < rowCount; row++ { matrix[row][0] = 0 } }}
func main() { wide := [][]int{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} check(slices.Equal(spiralOrder(wide), []int{1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7})) check(slices.Equal(spiralOrder([][]int{{1, 2}, {3, 4}}), []int{1, 2, 4, 3})) check(slices.Equal(spiralOrder([][]int{{5}, {6}, {7}}), []int{5, 6, 7})) check(slices.Equal(spiralOrder([][]int{{9}}), []int{9})) grid := [][]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}} rotate(grid) check(reflect.DeepEqual(grid, [][]int{{7, 4, 1}, {8, 5, 2}, {9, 6, 3}})) grid = [][]int{{1, 1, 1}, {1, 0, 1}, {1, 1, 1}} setZeroes(grid) check(reflect.DeepEqual(grid, [][]int{{1, 0, 1}, {0, 0, 0}, {1, 0, 1}})) grid = [][]int{{0, 1, 2, 0}, {3, 4, 5, 2}, {1, 3, 1, 5}} setZeroes(grid) check(reflect.DeepEqual(grid, [][]int{{0, 0, 0, 0}, {0, 4, 5, 0}, {0, 3, 1, 0}})) fmt.Println("ok: spiralOrder, rotate and setZeroes")}
func check(ok bool) { if !ok { panic("check failed") }}function spiralOrder(matrix: number[][]): number[] { // Spiral order with four walls around the part not yet taken. Take one side, then move that wall // inward. Every cell is taken once: O(m·n) time for m rows and n columns, O(1) extra memory // beyond the output. const result: number[] = []; let top = 0; let bottom = matrix.length - 1; let left = 0; let right = matrix[0].length - 1; while (top <= bottom && left <= right) { for (let column = left; column <= right; column++) { result.push(matrix[top][column]); } top++; for (let row = top; row <= bottom; row++) { result.push(matrix[row][right]); } right--; // The checks stop a single leftover row or column from being taken twice. if (top <= bottom) { for (let column = right; column >= left; column--) { result.push(matrix[bottom][column]); } bottom--; } if (left <= right) { for (let row = bottom; row >= top; row--) { result.push(matrix[row][left]); } left++; } } return result;}
function rotate(matrix: number[][]): void { // Rotate a square grid 90° clockwise in place. A transpose (swap across the main diagonal) // followed by reversing each row moves every cell to its rotated spot. O(n²) time for an n × n // grid, O(1) memory. const size = matrix.length; for (let row = 0; row < size; row++) { for (let column = row + 1; column < size; column++) { // column > row, so each pair swaps once, not twice [matrix[row][column], matrix[column][row]] = [matrix[column][row], matrix[row][column]]; } } for (const row of matrix) row.reverse();}
function setZeroes(matrix: number[][]): void { // Set a cell's whole row and column to 0 if it holds 0. The first row and first column store // the marks, so no extra grid is needed. Their own zeros are remembered in two flags first, // because the marks would hide them. O(m·n) time for m rows and n columns, O(1) memory. const rowCount = matrix.length; const columnCount = matrix[0].length; const firstRow = matrix[0].includes(0); const firstColumn = matrix.some((row) => row[0] === 0); for (let row = 1; row < rowCount; row++) { for (let column = 1; column < columnCount; column++) { // Mark the row and the column. if (matrix[row][column] === 0) matrix[row][0] = matrix[0][column] = 0; } } for (let row = 1; row < rowCount; row++) { for (let column = 1; column < columnCount; column++) { if (matrix[row][0] === 0 || matrix[0][column] === 0) matrix[row][column] = 0; } } // Clear the marker row and column last, so the marks are read before they change. if (firstRow) matrix[0].fill(0); if (firstColumn) for (const row of matrix) row[0] = 0;}
const check = (ok: boolean) => { if (!ok) throw new Error("check failed");};const same = (a: unknown, b: unknown) => JSON.stringify(a) === JSON.stringify(b);const wide = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]];check(same(spiralOrder(wide), [1, 2, 3, 4, 8, 12, 11, 10, 9, 5, 6, 7]));check(same(spiralOrder([[1, 2], [3, 4]]), [1, 2, 4, 3]));check(same(spiralOrder([[5], [6], [7]]), [5, 6, 7]));check(same(spiralOrder([[9]]), [9]));let grid = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];rotate(grid);check(same(grid, [[7, 4, 1], [8, 5, 2], [9, 6, 3]]));grid = [[1, 1, 1], [1, 0, 1], [1, 1, 1]];setZeroes(grid);check(same(grid, [[1, 0, 1], [0, 0, 0], [1, 0, 1]]));grid = [[0, 1, 2, 0], [3, 4, 5, 2], [1, 3, 1, 5]];setZeroes(grid);check(same(grid, [[0, 0, 0, 0], [0, 4, 5, 0], [0, 3, 1, 0]]));console.log("ok: spiralOrder, rotate and setZeroes");Look at the order of steps in set_zeroes. The first row and column are used as notes, so their
own zeros are saved in two flags before anything else. They are cleared last, after all the notes
are read. Change that order and a note gets erased before it’s used.
Variations
Section titled “Variations”Walking neighbors
Section titled “Walking neighbors”Many grid problems look at the four cells next to each cell: up, down, left and right. Keep a list of four direction pairs and loop over it. For each pair, compute the neighbor’s row and column, and skip it if it’s out of bounds. Add the four diagonal pairs when the problem says “eight neighbors”. This one loop is the core of flood fill, islands and shortest paths on a grid.
Rotate by layers
Section titled “Rotate by layers”The transpose-and-reverse trick is the easiest to get right. You can also rotate each ring directly. For each layer, take four cells at a time, one on each side, and move them around in a cycle: top to right, right to bottom, bottom to left, left to top. Save one cell in a temporary variable first. Both ways are O(n²) time for an n × n grid and O(1) memory. Counterclockwise is a transpose and then reversing each column.
Old and new values in one cell
Section titled “Old and new values in one cell”Game of Life updates every cell from its neighbors at the same moment. If you write new values directly, later cells read a mix of old and new. Encode both in the cell instead. For example, use 2 for “was alive, now dead” and 3 for “was dead, now alive”. Every neighbor can still tell the old value. A second pass turns 2 into 0 and 3 into 1.
A sorted grid as one array
Section titled “A sorted grid as one array”When every row is sorted and each row starts after the previous one ends, the grid is one sorted
array. Use binary search from 0 to row_count * column_count - 1,
and turn an index back into a cell with row = index / column_count and
column = index % column_count. When rows and columns are each sorted, but not joined, start at the
top right corner. A bigger target means moving down, a smaller one means moving left. Each step
rules out one row or one column, so it’s O(m + n).
Complexity
Section titled “Complexity”| Time | Extra memory | |
|---|---|---|
| Any full walk: spiral, transpose | O(m·n) | O(1) beyond output |
| Rotate in place | O(n²) | O(1) |
| Set zeroes with marker row and col | O(m·n) | O(1) |
| Set zeroes with two sets | O(m·n) | O(m + n) |
| Search a row and column sorted grid | O(m + n) | O(1) |
I’ll keep four walls around the part not yet visited. I take the top row and move the top wall down, then the right column, then the bottom row, then the left column. I check the walls before the last two sides, so a single leftover row isn’t taken twice. It’s O(m·n) time.
Interview questions
Section titled “Interview questions”Interviewer asksSet Matrix Zeroes with O(m + n) memory is easy. How do you get to O(1)?
Use the grid’s own first row and first column as the two sets. A zero at (row, column) marks
grid[row][0] and grid[0][column]. The first row and column hold real data too, so first save
two flags: does the first row have a zero, does the first column have one. Fill the inner cells
from the marks, then clear the first row and column last.
Interviewer asksWhy does transposing and then reversing each row rotate the grid clockwise?
In an n × n grid (size in code), after a 90° clockwise turn, the value at (row, column)
lands at (column, size - 1 - row). The transpose moves it to (column, row). Reversing the
row moves column row to column size - 1 - row. Together that’s exactly
(column, size - 1 - row). It only works in place on a square grid. A grid that isn’t square
needs a new grid, because its shape changes.
Interviewer asksThe grid is too big to fit in memory. What can still work?
Anything that reads row by row. Spiral order and rotation need random access, so they would need the grid split into tiles. A search in a row and column sorted grid reads only a path of m + n cells. Set Matrix Zeroes can be done in two passes over the rows, keeping only the set of columns to clear, O(n) memory.
Practice
Section titled “Practice”10 problems on LeetCode: 2 easy, 8 medium
- Transpose Matrix(opens LeetCode in a new tab)Easy
Row r, column c moves to row c, column r.
- Toeplitz Matrix(opens LeetCode in a new tab)Easy
Each cell only needs to match the one up and to the left.
- Valid Sudoku(opens LeetCode in a new tab)Medium
Row r and column c give the 3×3 box index directly.
- Spiral Matrix(opens LeetCode in a new tab)Medium
Four walls around the cells not yet taken; take a side, move that wall in.
- Spiral Matrix II(opens LeetCode in a new tab)Medium
The same walls as Spiral Matrix, writing instead of reading.
- Rotate Image(opens LeetCode in a new tab)Medium
A rotation is two simpler moves you can do in place.
- Set Matrix Zeroes(opens LeetCode in a new tab)Medium
Use the first row and column as the notebook for which lines to clear.
- Game of Life(opens LeetCode in a new tab)Medium
Encode old and new state in the same cell so neighbors still read the old one.
- Diagonal Traverse(opens LeetCode in a new tab)Medium
Every cell on one diagonal has the same r + c.
- Search a 2D Matrix II(opens LeetCode in a new tab)Medium
Start at the top right corner, where each comparison rules out a row or a column.
Ticks are saved in this browser only. Clear your site data and they're gone.
Three of these are solved step by step on this site, as part of the Blind 75: Spiral Matrix, Rotate Image and Set Matrix Zeroes. Grids also show up in Number of Islands, Pacific Atlantic Water Flow and Word Search, where the neighbor loop from this page drives a search.
- Use it when the input is a 2D grid and the work is a walk in a fixed order, a rotation, or a change in place.
- Walls for layers: take a side, move the wall in, and check before the last two sides.
- Neighbors come from a list of directions plus a bounds check.
- In place: keep notes in the first row and column, or encode old and new in one cell.
- Cost: O(m·n) to touch every cell, O(1) extra memory for all of the moves on this page.
- Watch for: rows and columns swapped, and grids that are a single row or column.
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.