Skip to main content

Difference Arrays and 2D Prefix Sums

Editorial

Many updates, then one read

Range Addition: start with 5 zeros and apply the updates [[1, 3, 2], [2, 4, 3], [0, 2, -2]], where [start, end, inc] adds inc to every position from start to end. The result is [-2, 0, 3, 5, 3].

The direct way adds to every cell of every range:

def get_modified_array_slow(length, updates):
    result = [0] * length
    for start, end, inc in updates:
        for i in range(start, end + 1):          # touches every index of every range
            result[i] += inc
    return result

It returns [-2, 0, 3, 5, 3], but each update costs the length of its range. With 100,000 positions and 10,000 updates that each cover everything, that's 1,000,000,000 additions, and nobody reads the array until the end.

Think about what an update really changes. Inside the range, neighbours still differ by what they differed by before; the only places the difference between neighbours changes are where the range starts (it jumps up by inc) and just after it ends (it drops back). So store differences, make two writes per update, and rebuild the array once with a running total, the tool from Introduction to Prefix Sums.

Two range updates on seven zeros: add 3 to positions 2 to 4, and add 2 to positions 4 to 6. Each update makes two writes in a difference array d of eight slots, the last one spare: +3 at 2 and −3 at 5, +2 at 4 and −2 at 7, so d is 0, 0, +3, 0, +2, −3, 0, −2. The running total of d is the final array: 0, 0, 3, 3, 5, 2, 2.

Two writes, one running total

A difference array d holds how much each position differs from the one before it, so the running total of d is the array itself. Adding inc to start..end raises the difference at start by inc and lowers it at end + 1 by inc; nothing in between changes. The running total picks inc up at start and carries it until it's cancelled just past end.

Give d one spare slot at the end, so end + 1 is a valid index even when the range reaches the last position:

Range Addition: 5 zeros, updates [[1, 3, 2], [2, 4, 3], [0, 2, -2]] — The top row is d, with its spare slot at the end; the bottom row is the array, filled by the running total of d.

vector<int> getModifiedArray(int length, const vector<vector<int>>& updates) {
    vector<int> diff(length + 1, 0);                // one spare slot, so end + 1 is always a valid index
    for (const auto& u : updates) {
        diff[u[0]] += u[2];                         // the range starts here
        diff[u[1] + 1] -= u[2];                     // and stops just after its last index
    }
    vector<int> result(length);
    int running = 0;
    for (int i = 0; i < length; i++) {
        running += diff[i];                         // the running total of the differences
        result[i] = running;
    }
    return result;
}
def get_modified_array(length, updates):
    diff = [0] * (length + 1)                    # one spare slot, so end + 1 is always a valid index
    for start, end, inc in updates:
        diff[start] += inc                       # the range starts here
        diff[end + 1] -= inc                     # and stops just after its last index
    result, running = [], 0
    for i in range(length):
        running += diff[i]                       # the running total of the differences
        result.append(running)
    return result
static int[] getModifiedArray(int length, int[][] updates) {
    int[] diff = new int[length + 1];               // one spare slot, so end + 1 is always a valid index
    for (int[] u : updates) {
        diff[u[0]] += u[2];                         // the range starts here
        diff[u[1] + 1] -= u[2];                     // and stops just after its last index
    }
    int[] result = new int[length];
    int running = 0;
    for (int i = 0; i < length; i++) {
        running += diff[i];                         // the running total of the differences
        result[i] = running;
    }
    return result;
}
function getModifiedArray(length, updates) {
    const diff = new Array(length + 1).fill(0);     // one spare slot, so end + 1 is always a valid index
    for (const [start, end, inc] of updates) {
        diff[start] += inc;                         // the range starts here
        diff[end + 1] -= inc;                       // and stops just after its last index
    }
    const result = [];
    let running = 0;
    for (let i = 0; i < length; i++) {
        running += diff[i];                         // the running total of the differences
        result.push(running);
    }
    return result;
}

Each prints -2 0 3 5 3, 0 0 3 3 5 2 2 and 1 1 1 for 5 zeros with [[1, 3, 2], [2, 4, 3], [0, 2, -2]], 7 zeros with [[2, 4, 3], [4, 6, 2]], and 3 zeros with [[0, 2, 1]]. q updates cost 2q writes, and the rebuild is one pass: O(n + q) time instead of O(n · q), and O(n) space.

Car Pooling: off at the stop, not after it

Car Pooling is the same idea with passengers: [passengers, from, to] get on at from and are off at to. So the −passengers goes at to itself, not at to + 1: a group getting off at 5 frees its seats for a group getting on at 5. Locations are at most 1000, so 1001 slots cover every stop.

def car_pooling(trips, capacity):
    change = [0] * 1001                          # stops 0..1000
    for passengers, start, stop in trips:
        change[start] += passengers              # they get on at start
        change[stop] -= passengers               # and are off at stop, before anyone gets on there
    in_car = 0
    for c in change:
        in_car += c
        if in_car > capacity:
            return False
    return True

It prints False, True and True for [[2, 1, 5], [3, 3, 7]] with capacity 4, the same trips with capacity 5, and [[2, 1, 5], [3, 5, 7]] with capacity 3. In the last one 2 people leave at 5 and 3 get on at 5, so the car never holds more than 3.

Check each problem for which kind of end it has. Range Addition's end is the last index that changes (write at end + 1). Car Pooling's to is the first stop where the passengers are gone (write at to).

Prefix sums on a grid

Range Sum Query 2D - Immutable asks for the sum of any rectangle of a matrix, many times. Do in two directions what Introduction to Prefix Sums did in one: P[r][c] is the sum of everything above and left of position (r, c), that is rows 0..r − 1 and columns 0..c − 1, with a row and a column of zeros in front.

Building it. P[r + 1][c + 1] is the new cell, plus the block above it, plus the block to its left. Those two blocks overlap in the block above-left, which got counted twice, so take it off once:

P[r + 1][c + 1] = grid[r][c] + P[r][c + 1] + P[r + 1][c] − P[r][c]

Building the table for [[1, 2, 3], [4, 5, 6], [7, 8, 9]] — Amber: the two blocks added. Violet: the overlap taken off. Blue: the new total.

Asking it. For rows r1..r2 and columns c1..c2, start from everything down to the bottom-right corner, take off the rows above the rectangle and the columns to its left, and add back the top-left block, which was taken off twice:

sum = P[r2 + 1][c2 + 1] − P[r1][c2 + 1] − P[r2 + 1][c1] + P[r1][c1]

Left, a 5 by 5 grid with the rectangle rows 2 to 3, columns 1 to 3 shaded: the rectangle you want. It equals four totals, each drawn as a small grid with the rectangle outlined: P[r2+1][c2+1], everything down to its bottom-right corner, minus P[r1][c2+1], the rows above it, minus P[r2+1][c1], the columns left of it, plus P[r1][c1], the top-left block that was taken away twice.
class NumMatrix {
    vector<vector<long long>> P;                    // P[r][c] = sum of rows 0..r-1 and columns 0..c-1
public:
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        P.assign(m + 1, vector<long long>(n + 1, 0));
        for (int r = 0; r < m; r++)
            for (int c = 0; c < n; c++)             // the block above and the block left both hold P[r][c]
                P[r + 1][c + 1] = matrix[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c];
    }

    long long sumRegion(int r1, int c1, int r2, int c2) {   // the corner was taken away twice
        return P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1];
    }
};
class NumMatrix:
    def __init__(self, matrix):
        m, n = len(matrix), len(matrix[0])
        self.P = [[0] * (n + 1) for _ in range(m + 1)]   # P[r][c] = sum of rows 0..r-1, columns 0..c-1
        for r in range(m):
            for c in range(n):                   # the block above and the block left both hold P[r][c]
                self.P[r + 1][c + 1] = matrix[r][c] + self.P[r][c + 1] + self.P[r + 1][c] - self.P[r][c]

    def sum_region(self, r1, c1, r2, c2):        # the corner was taken away twice
        P = self.P
        return P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1]
static class NumMatrix {
    private final long[][] P;                       // P[r][c] = sum of rows 0..r-1 and columns 0..c-1

    NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        P = new long[m + 1][n + 1];
        for (int r = 0; r < m; r++)
            for (int c = 0; c < n; c++)             // the block above and the block left both hold P[r][c]
                P[r + 1][c + 1] = matrix[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c];
    }

    long sumRegion(int r1, int c1, int r2, int c2) {    // the corner was taken away twice
        return P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1];
    }
}
class NumMatrix {
    constructor(matrix) {
        const m = matrix.length, n = matrix[0].length;
        this.P = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));   // rows 0..r-1, columns 0..c-1
        for (let r = 0; r < m; r++)
            for (let c = 0; c < n; c++)             // the block above and the block left both hold P[r][c]
                this.P[r + 1][c + 1] = matrix[r][c] + this.P[r][c + 1] + this.P[r + 1][c] - this.P[r][c];
    }

    sumRegion(r1, c1, r2, c2) {                     // the corner was taken away twice
        const P = this.P;
        return P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1];
    }
}

Each prints 8, 11, 12 and 58 for sumRegion(2, 1, 4, 3), sumRegion(1, 1, 2, 2), sumRegion(1, 2, 2, 4) and sumRegion(0, 0, 4, 4) (the whole matrix) on Range Sum Query 2D's example, where the four numbers are row1, col1, row2, col2. The build is O(m · n); each question is four lookups, O(1).

A block around every cell

Matrix Block Sum: for every cell, the sum of the block that reaches k cells out in each direction, cut off at the edges. Build the table once, then ask it one rectangle per cell, with the rectangle clamped to the grid:

def matrix_block_sum(mat, k):
    m, n = len(mat), len(mat[0])
    P = [[0] * (n + 1) for _ in range(m + 1)]
    for r in range(m):
        for c in range(n):
            P[r + 1][c + 1] = mat[r][c] + P[r][c + 1] + P[r + 1][c] - P[r][c]
    answer = [[0] * n for _ in range(m)]
    for i in range(m):
        for j in range(n):
            r1, c1 = max(0, i - k), max(0, j - k)        # clamp the block to the grid
            r2, c2 = min(m - 1, i + k), min(n - 1, j + k)
            answer[i][j] = P[r2 + 1][c2 + 1] - P[r1][c2 + 1] - P[r2 + 1][c1] + P[r1][c1]
    return answer

For [[1, 2, 3], [4, 5, 6], [7, 8, 9]] it prints [[12, 21, 16], [27, 45, 33], [24, 39, 28]] with k = 1 and [[45, 45, 45], [45, 45, 45], [45, 45, 45]] with k = 2 (every block covers the whole grid). O(m · n) in all, however big k is.

Both ideas at once

The two halves combine. To add 1 to many rectangles of a grid (Increment Submatrices by One), make four writes per rectangle, at its corners just past the edges, and rebuild with a 2D running total:

def range_add_queries(n, queries):
    diff = [[0] * (n + 1) for _ in range(n + 1)]     # one spare row and column
    for r1, c1, r2, c2 in queries:
        diff[r1][c1] += 1                            # four corners: the 2D form of +v and -v
        diff[r1][c2 + 1] -= 1
        diff[r2 + 1][c1] -= 1
        diff[r2 + 1][c2 + 1] += 1
    mat = [[0] * n for _ in range(n)]
    for r in range(n):
        for c in range(n):
            above = mat[r - 1][c] if r else 0
            left = mat[r][c - 1] if c else 0
            corner = mat[r - 1][c - 1] if r and c else 0
            mat[r][c] = diff[r][c] + above + left - corner   # a 2D running total
    return mat

For a 3 × 3 grid with the rectangles [[1, 1, 2, 2], [0, 0, 1, 1]] it prints [[1, 1, 0], [1, 2, 1], [0, 1, 1]].

Spotting it

Reach for a difference array when you see:

  • add v to every element from l to r, many times, then read the result once (bookings, flights, "apply these updates");
  • things that start and stop at points (passengers on and off, intervals covering a point): +1 where each starts, −1 where it stops.

Reach for a 2D table when you see the sum of a rectangle, asked many times, or a block around every cell.

And the near-misses:

  • reads mixed in between the updates: each read would need a fresh O(n) pass. That's a Fenwick or segment tree, in the Advanced section.
  • positions up to 10^9 with few events: an array that big is mostly empty, so sort the change points instead (the sweep line in Intervals).
  • the largest value in a range or rectangle: nothing to subtract, so no table of totals helps.

Common mistakes

Stopping at end instead of after it. diff[end] -= inc

The last position of every range misses its update. On 5 zeros with [[1, 3, 2], [2, 4, 3], [0, 2, -2]] it returns [-2, 0, 5, 3, 0] instead of [-2, 0, 3, 5, 3].

No spare slot. diff = [0] * length

Fine until a range reaches the last position. The same updates crash: IndexError: list index out of range. In Java it's an ArrayIndexOutOfBoundsException. Give d length + 1 slots.

Car Pooling with an inclusive end. change[stop + 1] -= passengers

Riders who are off at 5 still take up seats at 5. On [[2, 1, 5], [3, 5, 7]] with capacity 3 it returns False, though the car never holds more than 3.

Building the 2D table without taking off the overlap. matrix[r][c] + P[r][c + 1] + P[r + 1][c]

The block above-left is counted twice, and the error grows with every row. sumRegion(2, 1, 4, 3) on Range Sum Query 2D's example returns 295 instead of 8.

Clamping only one side in Matrix Block Sum. r1, c1 = i - k, j - k

Clamping the far side stops the crash, so this looks finished. But at cell (0, 0), i − k is −1, and Python reads P[-1], the table's last row, without complaint: it returns 9 instead of 12. Clamp both sides with max(0, …) and min(m − 1, …).

Check yourself

Quiz

1 / 6

Multiple choice

Add 5 to nums[2..6] with a difference array of n + 1 slots. Which two writes?

Practice problems

From easier to harder:

  1. Range Addition: the difference array.
  2. Car Pooling: the same, with riders off at to.
  3. Range Sum Query 2D - Immutable: build the table, answer with four totals.
  4. Matrix Block Sum: one clamped rectangle per cell.

Then the Prefix Sum Quiz checks the whole section.