Attic
CollectionsArrays

2D Arrays & Matrices

Array-of-arrays heap layout, rectangular grids, jagged arrays, 2D printing methods, matrix traversals, rotations, and deep cloning

A two-dimensional (2D) array represents data organized in rows and columns. In Java, matrices are not stored as a single, contiguous block of memory; rather, they are structured as an array of references to independent 1D array objects.


Mental Model: Array of Arrays

In languages like C or C++, a 2D array can be allocated as a single, contiguous block of rows * cols * sizeof(type) bytes in row-major order.

In Java, there is no single contiguous 2D block. A 2D array int[][] is an outer array whose elements are object references pointing to independent 1D int[] heap objects.


Rectangular Matrices

A rectangular matrix has the same number of columns in every row:

MatrixBasics.java
// 1. Declare and allocate 3 rows x 4 columns (all initialized to 0)
int rows = 3;
int cols = 4;
int[][] matrix = new int[rows][cols];

// 2. Direct 2D literal initialization
int[][] grid = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 3. Extracting dimensions safely
int numRows = grid.length;                     // 3
int numCols = (numRows > 0) ? grid[0].length : 0; // 3

Jagged (Ragged) Arrays

Because each row in Java is an independent array object, each row can possess a different length. These are called jagged arrays (or ragged arrays).

JaggedArray.java
// Leave the second dimension empty to allocate rows independently
int[][] jagged = new int[3][];

jagged[0] = new int[2]; // Row 0 has 2 columns
jagged[1] = new int[4]; // Row 1 has 4 columns
jagged[2] = new int[1]; // Row 2 has 1 column

// Populate jagged array
jagged[0][0] = 5;
jagged[0][1] = 9;
jagged[1][3] = 42;

Real-World Application: Pascal's Triangle

Jagged arrays avoid wasting heap memory when storing triangular or hierarchical data:

PascalsTriangle.java
public static int[][] generatePascalsTriangle(int numRows) {
    int[][] triangle = new int[numRows][];

    for (int i = 0; i < numRows; i++) {
        triangle[i] = new int[i + 1]; // Row i has (i + 1) elements
        triangle[i][0] = 1;          // First element is 1
        triangle[i][i] = 1;          // Last element is 1

        for (int j = 1; j < i; j++) {
            triangle[i][j] = triangle[i - 1][j - 1] + triangle[i - 1][j];
        }
    }
    return triangle;
}

Printing 2D Arrays & Matrices

Attempting to print a 2D array with Arrays.toString(matrix) outputs memory references for each row: [[I@15db9742, [I@6d06d69c]. Use one of the three idiomatic techniques below instead:

The standard JDK recursive method for multidimensional arrays of arbitrary depth:

PrintDeepToString.java
import java.util.Arrays;

int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

System.out.println(Arrays.deepToString(matrix));
// Output: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Matrix Traversal & Algorithmic Patterns

1. Row-Major vs Column-Major Traversal

MatrixTraversals.java
int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
int R = matrix.length;
int C = matrix[0].length;

// A. Row-Major Traversal (Cache Friendly - O(R * C))
for (int r = 0; r < R; r++) {
    for (int c = 0; c < C; c++) {
        int val = matrix[r][c]; // Sequential access along the row
    }
}

// B. Column-Major Traversal (Cache Unfriendly - O(R * C))
for (int c = 0; c < C; c++) {
    for (int r = 0; r < R; r++) {
        int val = matrix[r][c]; // Repeatedly jumps between different row objects
    }
}

Cache Miss Penalty: Row-major traversal is significantly faster on modern hardware. Because row elements sit contiguously in memory, accessing matrix[r][c] followed by matrix[r][c+1] utilizes the L1 cache line already fetched. In contrast, column-major access repeatedly dereferences different row pointers, inducing frequent L1/L2 cache misses.

2. Diagonals of a Square Matrix (N x N)

MatrixDiagonals.java
int n = matrix.length;

// Main Diagonal: row == col (Top-Left to Bottom-Right)
for (int i = 0; i < n; i++) {
    int mainDiagVal = matrix[i][i];
}

// Anti-Diagonal: row + col == n - 1 (Top-Right to Bottom-Left)
for (int i = 0; i < n; i++) {
    int antiDiagVal = matrix[i][n - 1 - i];
}

Advanced Grid & Matrix Algorithms: For graph-based grid explorations (BFS/DFS direction vectors) and perimeter peeling, see DSA Patterns: Array & Matrix Patterns.


Matrix Transformations

In-Place Transposition & 90° Clockwise Rotation

MatrixTransforms.java
// Transpose square N x N matrix in-place: swap matrix[r][c] with matrix[c][r]
public static void transpose(int[][] matrix) {
    int n = matrix.length;
    for (int r = 0; r < n; r++) {
        for (int c = r + 1; c < n; c++) {
            int temp = matrix[r][c];
            matrix[r][c] = matrix[c][r];
            matrix[c][r] = temp;
        }
    }
}

// Rotate 90 degrees Clockwise = Transpose + Reverse each row
public static void rotate90Clockwise(int[][] matrix) {
    transpose(matrix);
    int n = matrix.length;
    for (int r = 0; r < n; r++) {
        int left = 0, right = n - 1;
        while (left < right) {
            int temp = matrix[r][left];
            matrix[r][left] = matrix[r][right];
            matrix[r][right] = temp;
            left++;
            right--;
        }
    }
}

Cloning & The Shallow Copy Trap

Calling .clone() on a 2D array performs a shallow copy of the outer reference array only!

ShallowCopyTrap.java
int[][] matrix = {{1, 2}, {3, 4}};
int[][] copy = matrix.clone(); // TRAP!

// Mutating copy mutates original!
copy[0][0] = 999;
System.out.println(matrix[0][0]); // 999! (MUTATED!)

Why this fails: matrix.clone() creates a new outer array, but its entries are reference pointers that still point to the exact same inner int[] row objects in the heap.

Proper 2D Matrix Deep Copy

To produce a true, independent deep copy of a 2D matrix, clone each row individually:

DeepCopyMatrix.java
public static int[][] deepCopy2D(int[][] original) {
    int[][] copy = new int[original.length][];
    for (int i = 0; i < original.length; i++) {
        copy[i] = original[i].clone(); // or Arrays.copyOf(original[i], original[i].length)
    }
    return copy;
}

Custom Comparator Sorting on 2D Arrays

Frequently required in interval scheduling and coordinate geometry problems:

SortMatrix.java
int[][] intervals = {{1, 4}, {0, 2}, {3, 5}};

// Sort intervals by start time ascending:
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
// Result: [[0, 2], [1, 4], [3, 5]]

// Sort intervals by end time descending, tie-breaking by start time ascending:
Arrays.sort(intervals, (a, b) -> {
    int cmp = Integer.compare(b[1], a[1]);
    return (cmp != 0) ? cmp : Integer.compare(a[0], b[0]);
});

Complexity Profile

OperationTime ComplexitySpace ComplexityNotes / Invariants
Direct Cell Access (matrix[r][c])O(1)O(1)Two pointer dereferences: outer[r] then row[c]
Row-Major TraversalO(R * C)O(1)High CPU cache locality
Column-Major TraversalO(R * C)O(1)Stride cache miss penalty
In-Place Transposition (N x N)O(N^2)O(1)Swaps matrix[r][c] with matrix[c][r]
90° Rotation (N x N)O(N^2)O(1)Transpose + horizontal row reversal
Deep CopyO(R * C)O(R * C)Allocates new outer array and R inner row arrays
Arrays.deepEquals()O(R * C)O(1)Recursively checks element equality

2D Matrix Toolkit Reference

Static methods and operations commonly used when working with 2D matrices:

MethodParametersReturn TypeDescription / Notes
Arrays.deepToString(a)(Object[] a)StringRecursively formats multidimensional arrays as [[...], [...]]
Arrays.deepEquals(a1, a2)(Object[] a1, Object[] a2)booleanRecursively verifies equality across all rows and cells
Arrays.deepHashCode(a)(Object[] a)intComputes content-based hash code for multidimensional arrays
Arrays.fill(row, val)(int[] a, int val)voidFills an individual row; loop over for (int[] row : matrix) to fill entire matrix
Arrays.sort(matrix, comparator)(T[] a, Comparator c)voidSorts rows by column value (e.g. interval start/end times)
row.clone()()int[]Deep-copies a single row; call per row to produce an independent 2D deep copy

On this page