A complete guide to multidimensional arrays — how 2D and 3D arrays are stored in memory, row-major vs column-major order, all matrix operations with code and complexity, common matrix algorithms, and interview problems involving matrices.
Introduction
A one-dimensional array stores a sequence. A two-dimensional array (matrix) stores a grid of values — rows and columns. Three-dimensional arrays add depth, representing volumes or sequences of 2D grids.
2D arrays appear everywhere: image processing (pixels), spreadsheets (rows and columns), game boards, dynamic programming tables, adjacency matrices for graphs, and scientific simulations. Understanding how they are stored in memory and how to operate on them efficiently is essential.
Memory Layout — Row-Major vs Column-Major
This is a crucial concept for performance. How a 2D array is stored in linear memory affects cache performance dramatically.
Row-Major Order (C, Python, Java)
Elements are stored row by row. All elements of row 0, then all of row 1, etc.
| Address | 1000 1004 1008 1012 1016 1020 1024 1028 1032 1036 1040 1044 |
| Value | 1 2 3 4 5 6 7 8 9 10 11 12 |
| |←── row 0 ── | |←── row 1 ──→|←── row 2 ──→| |
| Address formula | base + (i × num_cols + j) × element_size |
| matrix[1][2] | base + (1×4 + 2) × 4 = base + 24 |
Column-Major Order (Fortran, MATLAB, R)
Elements are stored column by column.
Same matrix in column-major:
Memory: 1, 5, 9, 2, 6, 10, 3, 7, 11, 4, 8, 12
|col0| | col1 | | col2 | | col3 |
Cache line: When you access matrix[i][j], the CPU loads a cache line (typically 64 bytes = 16 integers) of adjacent memory.
Row-major traversal (row by row):
# FAST: traverses memory sequentially
for i in range(rows):
for j in range(cols):
process(matrix[i][j])
# Each cache line loaded is fully used by subsequent j iterations
Column-major traversal of row-major array:
# SLOW: jumps in memory (stride = num_cols × element_size)
for j in range(cols):
for i in range(rows):
process(matrix[i][j])
# Each cache line loaded has only 1 useful element (others are from different rows)
For large matrices (thousands of rows/columns), the difference is 5-10× in performance. Always prefer traversal that matches the storage order.
Matrix Operations
Traversal — O(m × n)
matrix = [[1,2,3],[4,5,6],[7,8,9]]
rows, cols = len(matrix), len(matrix[0])
# Row by row (efficient for row-major storage)
for i in range(rows):
for j in range(cols):
print(matrix[i][j], end=" ")
print()
# Using list comprehension
flat = [matrix[i][j] for i in range(rows) for j in range(cols)]
print(flat) # [1,2,3,4,5,6,7,8,9]
Transpose — O(m × n)
The transpose of a matrix swaps rows and columns: transposed[i][j] = original[j][i]
def transpose(matrix):
"""Transpose an m×n matrix → n×m matrix."""
m = len(matrix)
n = len(matrix[0])
result = [[0] * m for _ in range(n)]
for i in range(m):
for j in range(n):
result[j][i] = matrix[i][j]
return result
matrix = [[1,2,3],[4,5,6]] # 2×3
t = transpose(matrix)
print(t) # [[1,4],[2,5],[3,6]] — 3×2
# In-place transpose (only for square matrices)
def transpose_inplace(matrix):
n = len(matrix)
for i in range(n):
for j in range(i + 1, n): # Only above the diagonal
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
Matrix Rotation — O(n²)
Rotating an n×n matrix 90° clockwise:
Original: 1 2 3 After 90° CW: 7 4 1
4 5 6 8 5 2
7 8 9 9 6 3
Algorithm: Transpose then reverse each row.
def rotate_90_clockwise(matrix):
"""
Rotate n×n matrix 90° clockwise in-place.
Step 1: Transpose
Step 2: Reverse each row
"""
n = len(matrix)
# Step 1: Transpose (swap matrix[i][j] with matrix[j][i])
for i in range(n):
for j in range(i + 1, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# Step 2: Reverse each row
for i in range(n):
matrix[i].reverse()
matrix = [[1,2,3],[4,5,6],[7,8,9]]
rotate_90_clockwise(matrix)
print(matrix) # [[7,4,1],[8,5,2],[9,6,3]]
def rotate_90_counterclockwise(matrix):
"""Rotate 90° counter-clockwise = transpose + reverse each column."""
n = len(matrix)
# Transpose
for i in range(n):
for j in range(i + 1, n):
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
# Reverse each column (or reverse row order)
matrix.reverse()
Spiral Order Traversal — O(m × n)
Traverse a matrix in spiral order (top row, right column, bottom row reversed, left column reversed, then inner matrix).
def spiral_order(matrix):
"""
Return all elements in spiral order.
Example:
[[1, 2, 3], → [1, 2, 3, 6, 9, 8, 7, 4, 5]
[4, 5, 6],
[7, 8, 9]]
"""
result = []
if not matrix or not matrix[0]:
return result
top, bottom = 0, len(matrix) - 1
left, right = 0, len(matrix[0]) - 1
while top <= bottom and left <= right:
# Traverse top row: left → right
for col in range(left, right + 1):
result.append(matrix[top][col])
top += 1
# Traverse right column: top → bottom
for row in range(top, bottom + 1):
result.append(matrix[row][right])
right -= 1
# Traverse bottom row: right → left (if row still exists)
if top <= bottom:
for col in range(right, left - 1, -1):
result.append(matrix[bottom][col])
bottom -= 1
# Traverse left column: bottom → top (if column still exists)
if left <= right:
for row in range(bottom, top - 1, -1):
result.append(matrix[row][left])
left += 1
return result
matrix = [[1,2,3],[4,5,6],[7,8,9]]
print(spiral_order(matrix)) # [1,2,3,6,9,8,7,4,5]
Matrix Multiplication — O(n³)
def matrix_multiply(A, B):
"""
Multiply matrices A (m×k) and B (k×n) → result (m×n).
Requires: columns of A = rows of B
"""
m = len(A)
k = len(A[0])
n = len(B[0])
# Verify dimensions
assert len(B) == k, "Incompatible dimensions"
result = [[0] * n for _ in range(m)]
for i in range(m):
for j in range(n):
for p in range(k):
result[i][j] += A[i][p] * B[p][j]
return result
A = [[1,2],[3,4]]
B = [[5,6],[7,8]]
C = matrix_multiply(A, B)
print(C) # [[19,22],[43,50]]
# Verify: C[0][0] = 1×5 + 2×7 = 5+14 = 19 ✓
Set Matrix Zeros — O(m × n)
If any element is 0, set its entire row and column to 0.
def set_zeroes(matrix):
"""
Set entire row and column to zero if element is zero.
Uses first row and column as markers — O(1) extra space.
"""
m, n = len(matrix), len(matrix[0])
# Check if first row/column contain zeros (need to handle separately)
first_row_zero = any(matrix[0][j] == 0 for j in range(n))
first_col_zero = any(matrix[i][0] == 0 for i in range(m))
# Use first row and column as markers for other rows/columns
for i in range(1, m):
for j in range(1, n):
if matrix[i][j] == 0:
matrix[i][0] = 0 # Mark first cell of this row
matrix[0][j] = 0 # Mark first cell of this column
# Zero out cells based on markers (excluding first row and column)
for i in range(1, m):
for j in range(1, n):
if matrix[i][0] == 0 or matrix[0][j] == 0:
matrix[i][j] = 0
# Handle first row
if first_row_zero:
for j in range(n):
matrix[0][j] = 0
# Handle first column
if first_col_zero:
for i in range(m):
matrix[i][0] = 0
matrix = [[1,1,1],[1,0,1],[1,1,1]]
set_zeroes(matrix)
print(matrix) # [[1,0,1],[0,0,0],[1,0,1]]
3D Arrays
A 3D array adds a third dimension — useful for representing volumes, video frames, or sequences of matrices.
# 3D array: depth × rows × columns
# Example: 3 frames, each 2×3
video = [[[0 for _ in range(3)] for _ in range(2)] for _ in range(3)]
# Access: video[frame][row][col]
video[0][0][0] = 255 # Frame 0, row 0, col 0
video[1][1][2] = 128 # Frame 1, row 1, col 2
# Traverse 3D array
for frame in range(len(video)):
for row in range(len(video[0])):
for col in range(len(video[0][0])):
print(video[frame][row][col], end=" ")
print()
print("---")
NumPy Arrays — The Practical Choice
For numerical work, NumPy arrays are the standard — they are O(1) random access, support vectorized operations (no Python loop needed), and use optimized C/Fortran routines.
import numpy as np
# Create matrices
A = np.array([[1,2,3],[4,5,6]]) # 2×3
B = np.zeros((3, 3)) # 3×3 zeros
C = np.ones((2, 2)) # 2×2 ones
I = np.eye(3) # 3×3 identity matrix
# Operations — all vectorized, no explicit loops
A + B[:2,:3] # Element-wise addition
A * 2 # Scalar multiplication
A.T # Transpose
np.dot(A, A.T) # Matrix multiplication (2×3 × 3×2 = 2×2)
A @ A.T # Same with @ operator
np.linalg.det(np.eye(3)) # Determinant
np.sum(A, axis=0) # Sum along columns
np.sum(A, axis=1) # Sum along rows
A.max() # Maximum element
A.reshape(3, 2) # Reshape to 3×2
A.flatten() # Flatten to 1D: [1,2,3,4,5,6]
Common Matrix Patterns in Problems
| Pattern | Description | Example Problem |
|---|
| Row/column traversal | Process each row or column | Row sums, column averages |
| Diagonal traversal | Main diagonal, anti-diagonal | Sum of diagonals |
| Spiral traversal | Peel outer layer inward | Spiral order |
| Layer-by-layer | Concentric rectangles | Rotate 90° |
| Two-pointer on rows | Meet in middle for 2D | Search in sorted matrix |
| Prefix sums 2D | Fast submatrix sums | Count submatrices with sum |
| BFS/DFS on grid | Treat matrix as graph | Number of islands |
Summary
- 2D array: Array of arrays, accessed by
[row][col] - Row-major storage: Elements stored row by row (C, Python, Java) — iterate row-first for cache efficiency
- Creating 2D arrays: Use list comprehension
[[0]*n for _ in range(m)] — never [[0]*n]*m - Transpose: Swap
[i][j] with [j][i] — O(m×n) - 90° rotation: Transpose + reverse rows — O(n²) in-place
- Spiral traversal: Boundary shrinking approach — O(m×n)
- Matrix multiply: Triple nested loop — O(n³) naive, O(n^2.807) Strassen
- 3D arrays: Add a third dimension index
- For numerical work, use NumPy — vectorized operations with optimized C back-end
*Next Lesson: Sliding Window Technique — One of the most important array techniques for solving subarray problems efficiently.*