Matrices and 2D Grids

Matrices represent images, game boards, and maps. Many classic problems reduce to transforming matrices, traversing them, or treating grids as graphs for search.

Conventions

Rows indexed $0..R-1$, columns $0..C-1$; cell $(r,c)$.

Assume a rectangular matrix: every row has the same number of columns. Handle empty inputs before reading the first row. In pseudocode, a..b includes both endpoints and is empty if its bounds oppose the stated step direction. Variables initialized to example dimensions should be replaced with the actual dimensions in a general implementation.

Rows increase down, columns increase right. Think “top-left is $(0,0)$”, not a Cartesian origin.

Visual index map (example $R=6$, $C=8$; each cell labeled $rc$):

  c →    0    1    2    3    4    5    6    7
r ↓   +----+----+----+----+----+----+----+----+
0     | 00 | 01 | 02 | 03 | 04 | 05 | 06 | 07 |
      +----+----+----+----+----+----+----+----+
1     | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
      +----+----+----+----+----+----+----+----+
2     | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
      +----+----+----+----+----+----+----+----+
3     | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 |
      +----+----+----+----+----+----+----+----+
4     | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
      +----+----+----+----+----+----+----+----+
5     | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
      +----+----+----+----+----+----+----+----+

For $C>0$, the following conversions describe a row-major flattened representation. An array of row objects need not itself occupy one contiguous block of memory:

  • Linear index: $\text{id}=r\cdot C+c$.
  • From id: $r=\lfloor \text{id}/C \rfloor$, $c=\text{id}\bmod C$.
  • Row-major scan order (common in problems): for $r$ in $0..R-1$, for $c$ in $0..C-1$.

Row-major and column-major visit order (same $3\times 6$ grid):

Row-major visit numbers:          Column-major visit numbers:
 0  1  2  3  4  5                 0  3  6  9 12 15
 6  7  8  9 10 11                 1  4  7 10 13 16
12 13 14 15 16 17                 2  5  8 11 14 17

Neighborhoods: $\mathbf{4}$-dir $\Delta={(-1,0),(1,0),(0,-1),(0,1)}$; $\mathbf{8}$-dir adds diagonals.

The offsets $(\Delta r,\Delta c)$ are applied as $(r+\Delta r,\ c+\Delta c)$.

4-neighborhood (“+”):

#
		   (r-1,c)
			 ↑
  (r,c-1) ← (r,c) → (r,c+1)
			 ↓
		   (r+1,c)

8-neighborhood (“×” adds diagonals):

(r-1,c-1)   (r-1,c)   (r-1,c+1)
	  \       ↑       /
	   \      │      /
(r,c-1) ←———  (r,c)  ———→ (r,c+1)
	   /      │      \
	  /       ↓       \
(r+1,c-1)   (r+1,c)   (r+1,c+1)

Typical direction arrays (keep them consistent to avoid bugs):

// 4-dir
dr = [-1, 1,  0, 0]
dc = [ 0, 0, -1, 1]

// 8-dir
dr8 = [-1,-1,-1, 0, 0, 1, 1, 1]
dc8 = [-1, 0, 1,-1, 1,-1, 0, 1]

Boundary checks (always guard neighbors):

0 ≤ nr < R  and  0 ≤ nc < C

Edge/inside intuition:

	   out of bounds
	┌─────────────────┐
	│ · · · · · · · · │
	│ · +---+---+---+ │
	│ · | a | b | c | │   ← valid cells
	│ · +---+---+---+ │
	│ · | d | e | f | │
	│ · +---+---+---+ │
	│ · · · · · · · · │
	└─────────────────┘

Basic Operations (Building Blocks)

Transpose

Swap across the main diagonal: $A_{r,c} \leftrightarrow A_{c,r}$ (square). For non-square, result shape is $C\times R$.

Example inputs and outputs:

Example 1 (square)

$$ A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \quad\Rightarrow\quad A^{\mathsf{T}} = \begin{bmatrix} 1 & 4 & 7 \\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{bmatrix} $$

Mathematical formula (3×3)

$$ (A^T)_{r,c}=A_{c,r},\quad 0\le r,c<3 $$

Pseudocode (square, in-place)

n = 3  # for this example; generalize to n = size
for r in 0..n-1:
  for c in r+1..n-1:
    swap A[r][c], A[c][r]

Example 2 (rectangular)

$$ \text{Input: } \quad A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \ (2 \times 3) $$

$$ \text{Output: } \quad A^{\mathsf{T}} = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix} \ (3 \times 2) $$

Mathematical formula (2×3 → 3×2)

$$ (A^{\mathsf T})_{r,c}=A_{c,r},\quad 0\le r<3,\ 0\le c<2 $$

Pseudocode (rectangular, new matrix)

R, C = 2, 3
B = zeros(C, R)
for r in 0..R-1:
  for c in 0..C-1:
    B[c][r] = A[r][c]

How it works:

Iterate pairs once and swap. For square matrices, can be in-place by visiting only $c>r$.

  • Time: $O(R\cdot C)$
  • Space: $O(1)$ in-place (square), else $O(R\cdot C)$ to allocate

Reverse Rows (Horizontal Flip)

Reverse each row left $\leftrightarrow$ right.

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 3 & 2 & 1 \\ 6 & 5 & 4 \end{bmatrix} $$

Mathematical formula (2×3)

$$ B_{r,c}=A_{r,\ C-1-c},\quad 0\le r<2,\ 0\le c<3 $$

Pseudocode (in-place)

R, C = 2, 3
for r in 0..R-1:
  for c in 0..(C//2 - 1):
    swap A[r][c], A[r][C-1-c]
  • Time: $O(R\cdot C)$
  • Space: $O(1)$

Reverse Columns (Vertical Flip)

Reverse each column top $\leftrightarrow$ bottom.

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 7 & 8 & 9 \\ 4 & 5 & 6 \\ 1 & 2 & 3 \end{bmatrix} $$

Mathematical formula (3×3)

$$ B_{r,c}=A_{R-1-r,\ c},\quad 0\le r,c<3 $$

Pseudocode (in-place)

R, C = 3, 3
for r in 0..(R//2 - 1):
  for c in 0..C-1:
    swap A[r][c], A[R-1-r][c]
  • Time: $O(R\cdot C)$
  • Space: $O(1)$

Rotations (Composed from Basics)

Use transpose and reversal for square in-place quarter turns. A 90° or 270° turn changes an $R\times C$ matrix into a $C\times R$ matrix; the straightforward rectangular algorithm allocates a new result. A 180° rotation preserves the shape and can be done in place for any rectangle.

90° Clockwise (CW)

Transpose, then reverse each row.

Example inputs and outputs:

Example 1 (3×3)

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 7 & 4 & 1 \\ 8 & 5 & 2 \\ 9 & 6 & 3 \end{bmatrix} $$

Mathematical formula (n×n)

$$ B_{r,c}=A_{n-1-c,\ r},\quad 0\le r,c<n $$

Pseudocode (square, in-place via basics)

n = 3
# transpose
for r in 0..n-1:
  for c in r+1..n-1:
    swap A[r][c], A[c][r]
# reverse each row
for r in 0..n-1:
  for c in 0..(n//2 - 1):
    swap A[r][c], A[r][n-1-c]

Example 2 (2×3 → 3×2)

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 4 & 1 \\ 5 & 2 \\ 6 & 3 \end{bmatrix} $$

Mathematical formula (R×C → C×R)

$$ B_{r,c}=A_{R-1-c,\ r},\quad 0\le r<C,\ 0\le c<R\ \ (R{=}2,\ C{=}3) $$

Pseudocode (rectangular, direct mapping)

R, C = 2, 3
B = zeros(C, R)
for r in 0..C-1:      # rows of B
  for c in 0..R-1:    # cols of B
    B[r][c] = A[R-1-c][r]

How it works:

Transpose swaps axes; reversing each row aligns columns to rows of the rotated image.

  • Time: $O(R\cdot C)$
  • Space: $O(1)$ in-place for square, else $O(R\cdot C)$ new

90° Counterclockwise (CCW)

Transpose, then reverse each column (or reverse rows, then transpose).

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 3 & 6 & 9 \\ 2 & 5 & 8 \\ 1 & 4 & 7 \end{bmatrix} $$

Mathematical formula (n×n)

$$ B_{r,c}=A_{c,\ n-1-r},\quad 0\le r,c<n $$

Pseudocode (square, via basics)

n = 3
# transpose
for r in 0..n-1:
  for c in r+1..n-1:
    swap A[r][c], A[c][r]
# reverse each column (vertical flip)
for r in 0..(n//2 - 1):
  for c in 0..n-1:
    swap A[r][c], A[n-1-r][c]

Pseudocode (general, direct mapping)

# A is R x C, B is C x R
B = zeros(C, R)
for r in 0..C-1:
  for c in 0..R-1:
    B[r][c] = A[c][C-1-r]

How it works:

Transpose, then flip vertically to complete the counterclockwise rotation.

  • Time: $O(R\cdot C)$
  • Space: $O(1)$ (square) or $O(R\cdot C)$

180° Rotation

Equivalent to reversing rows, then reversing columns (or two 90° rotations).

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } \begin{bmatrix} 9 & 8 & 7 \\ 6 & 5 & 4 \\ 3 & 2 & 1 \end{bmatrix} $$

Mathematical formula (R×C → R×C)

$$ B_{r,c}=A_{R-1-r,\ C-1-c},\quad 0\le r<R,\ 0\le c<C $$

Pseudocode (in-place via two flips)

R, C = 3, 3  # generalize as needed
# reverse rows
for r in 0..R-1:
  for c in 0..(C//2 - 1):
    swap A[r][c], A[r][C-1-c]
# reverse columns
for r in 0..(R//2 - 1):
  for c in 0..C-1:
    swap A[r][c], A[R-1-r][c]

Pseudocode (direct mapping to new matrix)

B = zeros(R, C)
for r in 0..R-1:
  for c in 0..C-1:
    B[r][c] = A[R-1-r][C-1-c]

How it works:

Horizontal and vertical flips relocate each element to $(R-1-r,\ C-1-c)$.

  • Time: $O(R\cdot C)$.
  • Auxiliary space: $O(1)$ for the two-flip implementation, for both square and rectangular matrices; $O(R\cdot C)$ for the separately allocated result.

270° Rotation

270° CW = 90° CCW; 270° CCW = 90° CW. Reuse the 90° procedures.

Mathematical formulas (general)

270° CW (i.e., 90° CCW):

$$ B_{r,c}=A_{c,\ C-1-r},\quad B\in\mathbb{R}^{C\times R}. $$

270° CCW (i.e., 90° CW):

$$ B_{r,c}=A_{R-1-c,\ r},\quad B\in\mathbb{R}^{C\times R}. $$

The two alternatives below each start from the original matrix; running both in sequence would undo the rotation.

Pseudocode (via composition, square in-place)

# 270° CW == 90° CCW
transpose(A)
reverse_columns_in_place(A)

# 270° CCW == 90° CW
transpose(A)
reverse_rows_in_place(A)

Layer-by-Layer (Square) 90° CW

Rotate each ring by cycling 4 positions.

How it works:

For layer $\ell$ with bounds $ [\ell..n-1-\ell]$, for each offset move:

top ← left, left ← bottom, bottom ← right, right ← top

Mathematical mapping (per moved element)

For an $n\times n$ matrix and a position $(r,c)$ on layer $\ell$, a 90° CW rotation sends

$$ (r,c)\ \mapsto\ (c,\ n-1-r)\ \mapsto\ (n-1-r,\ n-1-c)\ \mapsto\ (n-1-c,\ r) $$

Pseudocode (explicit loops, in-place)

n = size(A)
for layer in 0..(n//2 - 1):
  first = layer
  last  = n - 1 - layer
  for i in first..last-1:
    offset = i - first

    # save top
    tmp = A[first][i]

    # left -> top
    A[first][i] = A[last - offset][first]

    # bottom -> left
    A[last - offset][first] = A[last][last - offset]

    # right -> bottom
    A[last][last - offset] = A[i][last]

    # top (saved) -> right
    A[i][last] = tmp
  • Time: $O(n^{2})$
  • Space: $O(1)$

Traversal Patterns

Spiral Order

Read outer layer, then shrink bounds.

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} 1 & 2 & 3 & 4 \\ 5 & 6 & 7 & 8 \\ 9 & 10 & 11 & 12 \end{bmatrix} $$

$$ \text{Output sequence: } 1,2,3,4,8,12,11,10,9,5,6,7 $$

Mathematical formulation (general $R\times C$)

Let the matrix indices be $(r,c)$ with $0\le r<R,\ 0\le c<C$. For layer $\ell=0,1,\dots,L-1$ where $L=\left\lceil \tfrac{\min(R,C)}{2}\right\rceil$, set

$$ t=\ell,\quad b=R-1-\ell,\quad left=\ell,\quad rgt=C-1-\ell $$

Visit, in order:

  • Top edge: $(t,c)$ for $c=left,\ldots,rgt$.
  • Right edge: $(r,rgt)$ for $r=t+1,\ldots,b$.
  • Bottom edge (if $b>t$): $(b,c)$ for $c=rgt-1,\ldots,left$ (decreasing).
  • Left edge (if $rgt>left$): $(r,left)$ for $r=b-1,\ldots,t+1$ (decreasing).

Concatenate these per layer until all elements are visited.

Pseudocode (loops)

R, C = dims(A)
top, bottom = 0, R - 1
left, right = 0, C - 1
out = []

while top <= bottom and left <= right:
  # top row
  for c in left..right:
    out.append(A[top][c])
  top += 1

  # right column
  for r in top..bottom:
    out.append(A[r][right])
  right -= 1

  # bottom row (if any)
  if top <= bottom:
    for c in right..left step -1:
      out.append(A[bottom][c])
    bottom -= 1

  # left column (if any)
  if left <= right:
    for r in bottom..top step -1:
      out.append(A[r][left])
    left += 1

How it works:

Maintain top, bottom, left, right. Walk edges in order; after each edge, move the corresponding bound inward.

  • Time: $O(R\cdot C)$
  • Space: $O(1)$ beyond output.

Diagonal Order (r+c layers)

Visit cells grouped by $s=r+c$; alternate direction per diagonal to keep locality if desired.

Example inputs and outputs:

Example

$$ \text{Input: } \begin{bmatrix} a & b & c \\ d & e & f \end{bmatrix} \quad\Rightarrow\quad \text{One order: } a, b, d, e, c, f $$

Mathematical formulation (general $R\times C$)

Let $s=r+c$. For $s=0,1,\dots,R+C-2$, define

$$ r_{\min}(s)=\max\!\big(0,\ s-(C-1)\big) $$

$$ r_{\max}(s)=\min\!\big(R-1,\ s\big) $$

The diagonal set is $\{(r,s-r)\mid r_{\min}(s)\le r\le r_{\max}(s)\}$.

Traverse with alternating direction:

$$ \begin{cases} \text{if } s \text{ even: } r=r_{\max}(s), r_{\max}(s)-1,\dots,r_{\min}(s);\\[2pt] \text{if } s \text{ odd: } r=r_{\min}(s), r_{\min}(s)+1,\dots,r_{\max}(s). \end{cases} $$

(This parity choice reproduces the example order $a,b,d,e,c,f$ for $R=2,C=3$.)

Pseudocode (loops, alternating direction)

R, C = dims(A)
out = []

for s in 0..(R + C - 2):
  r_lo = max(0, s - (C - 1))
  r_hi = min(R - 1, s)

  if s % 2 == 0:
    # even s: go upward-right (decreasing r)
    for r in r_hi..r_lo step -1:
      c = s - r
      out.append(A[r][c])
  else:
    # odd s: go downward-left (increasing r)
    for r in r_lo..r_hi:
      c = s - r
      out.append(A[r][c])
  • Time: $O(R\cdot C)$
  • Space: $O(1)$ beyond the $O(RC)$ output list.

Grids as Graphs

Each cell is a node; edges connect neighboring walkable cells.

Grid-as-graph view (4-dir edges). Each cell is a node; edges connect neighbors that are “passable”. Great for BFS shortest paths on unweighted grids.

Example map (walls #, free ., start S, target T).

First the map, then BFS distances from S after traversing all reachable cells. Digits show distance modulo 10; X marks the target.

Original Map:
#####################
#S..#....#....#.....#
#.#.#.##.#.##.#.##..#
#.#...#..#.......#.T#
#...###.....###.....#
#####################

BFS layers (distance mod 10):
#####################
#012#8901#9012#45678#
#1#3#7##2#8##1#3##89#
#2#456#43#7890123#7X#
#345###54567###34567#
#####################

Legend: walls (#), goal reached (X)

BFS layers increase shortest-path distance from the source by one. A particular grid move can increase or decrease Manhattan distance, and walls can force detours. For this map the target distance is 28. Time and auxiliary space are $O(RC)$ with a visited matrix and queue.

Obstacles / costs / diagonals.

  • Obstacles: skip neighbors that are # (or where cost is $\infty$).
  • Weighted grids: use Dijkstra for non-negative costs, or 0–1 BFS with a deque when every edge costs either zero or one.
  • 8-dir with Euclidean costs: use $1$ for orthogonal moves and $\sqrt{2}$ for diagonals (A* often pairs well here with an admissible heuristic).

Common symbols:

. = free cell      # = wall/obstacle
S = start          T = target/goal
V = visited        * = on current path / frontier

BFS Shortest Path (Unweighted)

Find the minimum steps from S to T.

Example inputs and outputs:

Example

$$ \text{Grid (0 = open, 1 = wall), } S = (0,0), T = (2,3) $$

$$ \begin{bmatrix} S & 0 & 1 & 0 \\ 0 & 0 & 0 & 0 \\ 1 & 1 & 0 & T \end{bmatrix} \quad\Rightarrow\quad \text{Output: distance } = 5 $$

How it works:

Push S to a queue, expand in 4-dir layers, track distance/visited; stop when T is dequeued.

  • Time: $O(R\cdot C)$
  • Space: $O(R\cdot C)$

Connected Components (Islands)

Count regions of ‘1’s via DFS/BFS using four-directional adjacency. Here 1 means land, unlike the preceding shortest-path example where 1 means wall. With eight-directional adjacency, the diagonal cells in this example form one island instead of two.

Example inputs and outputs:

$$ \text{Input: } \begin{bmatrix} 1 & 1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix} \quad\Rightarrow\quad \text{Output: } 2 \ \text{islands} $$

How it works:

Scan cells; when an unvisited ‘1’ is found, flood it (DFS/BFS) to mark the whole island.

  • Time: $O(R\cdot C)$
  • Space: $O(R\cdot C)$ worst-case

Backtracking on Grids

Word Search (Single Word)

Find a word by moving to adjacent cells (4-dir), using each cell once per path.

Example inputs and outputs:

$$ \text{Board: } \begin{bmatrix} A & B & C & E \\ S & F & C & S \\ A & D & E & E \end{bmatrix}, \quad \text{Word: } "ABCCED" \quad\Rightarrow\quad \text{Output: true} $$

Mathematical formulation (general)

Let the word be $W=W_0W_1\cdots W_{L-1}$ and the grid be $G\in\Sigma^{R\times C}$. We seek a path $P=\big((r_0,c_0),\ldots,(r_{L-1},c_{L-1})\big)$ such that

$$ \begin{aligned} &\text{(match)} && G_{r_i,c_i}=W_i,\quad i=0,\ldots,L-1;\\ &\text{(adjacent)} && |r_{i+1}-r_i|+|c_{i+1}-c_i|=1,\quad i=0,\ldots,L-2;\\ &\text{(no reuse)} && (r_i,c_i)\neq(r_j,c_j)\ \text{for all }i\ne j. \end{aligned} $$

Instantiation for the example (one valid path)

$$ P=\big((0,0),(0,1),(0,2),(1,2),(2,2),(2,1)\big) $$

gives $A\to B\to C\to C\to E\to D = \text{"ABCCED"}$.

Pseudocode (DFS with loops over starts and 4-neighbors)

R, C = dims(board)
L = len(word)
if L == 0:
  return true
if R == 0 or C == 0 or L > R * C:
  return false
visited = array(R, C, fill=false)
dr = [1, -1, 0, 0]
dc = [0, 0, 1, -1]

def dfs(r, c, i):
  if r < 0 or r >= R or c < 0 or c >= C:
    return false
  if visited[r][c] or board[r][c] != word[i]:
    return false
  if i == L - 1:
    return true

  visited[r][c] = true
  for k in 0..3:
    nr = r + dr[k]
    nc = c + dc[k]
    if dfs(nr, nc, i + 1):
      visited[r][c] = false
      return true
  visited[r][c] = false
  return false

# try every starting cell
for r in 0..R-1:
  for c in 0..C-1:
    if dfs(r, c, 0):
      return true
return false

How it works:

Start from each cell matching the first character and search for the next character among adjacent cells. Mark cells only for the current path and restore those marks when returning. A failed attempt from one starting cell must not block another attempt.

  • Time: $O(RC\,4^L)$ is a simple upper bound. After the first move there are at most three forward choices because the previous cell cannot be reused, giving the tighter conventional bound $O(RC\,3^L)$.
  • Auxiliary space: $O(RC+L)$ for the shown visited matrix and recursion stack. Marking cells temporarily in place can reduce this to $O(L)$, provided all original values are restored.

Pruning: early letter mismatch; frequency precheck; prefix trie when searching many words.

Crossword-style Fill (Multiple Words)

Place words to slots with crossings; verify consistency at intersections.

Mathematical formulation

Let $S$ be the set of slots (across/down). Each slot $s\in S$ has a length $\ell(s)$ and ordered cell coordinates $\mathrm{cells}(s) = \big((r_0,c_0),\ldots,(r_{\ell(s)-1},c_{\ell(s)-1})\big)$. Let $D$ be the dictionary; define $D_\ell=\{w\in D:|w|=\ell\}$. Known letters from the grid induce a pattern constraint $P_s\in(\Sigma\cup\{_\})^{\ell(s)}$.

Find an assignment $f:S\to D$ such that, for all $s\in S$,

$$ f(s)\in D_{\ell(s)}\quad\text{and}\quad \forall i\ (P_s[i]\neq \text{\_} \Rightarrow f(s)[i]=P_s[i]), $$

and for every intersection between slots $s$ at index $i$ and $t$ at index $j$,

$$ f(s)[i]=f(t)[j]. $$

(Optionally enforce all-different: $s\neq t \Rightarrow f(s)\neq f(t)$.)

Domains hold words of the correct length that match fixed letters. Forward checking removes words incompatible with a newly assigned crossing; every removal is recorded so it can be undone. Minimum remaining values (MRV) chooses the unassigned slot with the fewest currently available words, breaking ties by the largest number of unassigned neighbors.

Pseudocode (backtracking with dynamic MRV and forward checking)

# Preprocess
slots = extract_slots(grid)                  # with cells(s) and pattern P_s

# Build initial domains from patterns and lengths
domains = dict()
for s in slots:
  domains[s] = { w in dictionary | len(w) == len(s) and matches_pattern(w, P_s) }

used = set()          # if words must be unique
assignment = dict()

def consistent(s, w):
  # check crossings vs assigned neighbors
  for each intersection (s,i) with (t,j):
    if t in assignment and assignment[t][j] != w[i]:
      return false
  return true

def forward_check_update_domains(s, w, removed):
  for each intersection (s,i) with (t,j):
    if t in assignment:
      continue
    for each v in copy(domains[t]):
      if v[j] != w[i]:
        domains[t].remove(v); removed.append((t, v))

def undo_forward_check(removed):
  for (t, v) in removed:
    domains[t].add(v)

def backtrack(idx):
  if idx == len(slots):
    return true

  remaining = [slot for slot in slots if slot not in assignment]
  s = min(remaining, key=(size(domains[slot] - used), -unassigned_neighbors(slot)))

  # iterate candidates; optionally skip ones already used
  for w in iterate(domains[s]):
    if w in used:
      continue
    if not consistent(s, w):
      continue

    assignment[s] = w
    used.add(w)
    removed = []
    forward_check_update_domains(s, w, removed)

    if all(domains[slot] - used for slot in slots if slot not in assignment) and backtrack(idx + 1):
      return true

    undo_forward_check(removed)
    used.remove(w)
    del assignment[s]

  return false

# Start search
if backtrack(0):
  return assignment  # filled crossword
else:
  return failure

How it works:

Backtrack over slot assignments, recomputing the most constrained slot after each placement. The shown version enforces distinct words through used; remove that restriction consistently if reuse is allowed. A trie is an optional way to generate candidates matching a pattern; it is not needed by this explicit-domain version.

  • Time: exponential in the number of slots in the worst case. With $S$ slots and at most $D$ candidates per slot, there can be $D^S$ assignments before accounting for constraint-checking costs.
  • Space: the initial domains, the current assignment, and the reversible domain-removal log, plus an $O(S)$ recursion stack.