Prefix sums and difference arrays
A prefix array stores P[k] = xs[0] + … + xs[k-1], built in one O(n) pass. Then the sum of any range xs[i..j] is P[j+1] − P[i], O(1) per query. A difference array is the reverse trick: each “add d to [lo..hi]” becomes two writes, and one prefix-sum pass at the end rebuilds the values.
The idea in plain words
Section titled “The idea in plain words”Think of a car’s odometer. To know how far you drove between Tuesday and Friday, you don’t re-add every trip. You read the odometer on Friday night and subtract Tuesday morning’s reading.
A prefix array is that odometer for a list. P[k] is “total distance before index k”. Any stretch is one reading minus another:
P[j+1]covers everything up to and includingj.P[i]covers everything beforei.- Their difference is exactly
xs[i] + … + xs[j].
The difference array runs the idea backwards. Instead of many reads, you have many range writes (“add 5 to every seat in rows 3 to 40”). Write +5 where the effect starts and −5 just after it ends. A running total over those markers switches each effect on and off at the right places.
See it
Section titled “See it”Step through the build, then click any two cells in the second panel. However long the range, the query is one subtraction.
When to reach for it
Section titled “When to reach for it”| The problem says… | Why prefix sums fit |
|---|---|
“sum (or count) over range [i, j]”, many queries, array doesn’t change |
O(n) once, then O(1) per query instead of O(n). |
“how many elements in [i, j] satisfy X” |
Prefix-sum a 0/1 indicator array. |
| “sum of a sub-rectangle”, repeated on a grid | 2-D prefix sums (a summed-area table). |
“add v to every element in [lo, hi]”, many updates, read at the end |
Difference array: O(1) per update, O(n) to rebuild. |
| “subarray whose sum equals k”, values can be negative | Prefix sums turn “range sum” into “difference of two prefixes”, which a hash map can look up. |
| “how many intervals cover point x” (bookings, shifts, ranges on a line) | +1 at each start, −1 after each end, then prefix-sum. |
How it works
Section titled “How it works”1-D prefix sums
Section titled “1-D prefix sums”- Allocate
Pwith n + 1 slots andP[0] = 0. For eachk, setP[k+1] = P[k] + xs[k]. - Answer
sum(xs[i..j])asP[j+1] − P[i].
def build_prefix(xs: list[int]) -> list[int]: """p[i] = sum of xs[0..i-1]. One extra slot so p[0] = 0 (the empty prefix).""" p = [0] * (len(xs) + 1) for i, x in enumerate(xs): p[i + 1] = p[i] + x # 1. each prefix extends the previous one return p
def range_sum(p: list[int], i: int, j: int) -> int: """Sum of xs[i..j] inclusive, in O(1).""" return p[j + 1] - p[i] # 2. everything up to j, minus everything before i
if __name__ == "__main__": xs = [3, -1, 4, 1, 5, 9, -2, 6] p = build_prefix(xs) assert p == [0, 3, 2, 6, 7, 12, 21, 19, 25] assert range_sum(p, 2, 5) == 4 + 1 + 5 + 9 assert range_sum(p, 0, 7) == sum(xs) assert range_sum(p, 3, 3) == 1 # a single element # Brute-force cross-check on every range. for i in range(len(xs)): for j in range(i, len(xs)): assert range_sum(p, i, j) == sum(xs[i : j + 1]) print("ok")import assert from "node:assert/strict";
/** p[i] = sum of xs[0..i-1]. One extra slot so p[0] = 0 (the empty prefix). */export function buildPrefix(xs: number[]): number[] { const p = new Array<number>(xs.length + 1).fill(0); for (let i = 0; i < xs.length; i++) { p[i + 1] = p[i] + xs[i]; // 1. each prefix extends the previous one } return p;}
/** Sum of xs[i..j] inclusive, in O(1). */export function rangeSum(p: number[], i: number, j: number): number { return p[j + 1] - p[i]; // 2. everything up to j, minus everything before i}
const xs = [3, -1, 4, 1, 5, 9, -2, 6];const p = buildPrefix(xs);assert.deepEqual(p, [0, 3, 2, 6, 7, 12, 21, 19, 25]);assert.equal(rangeSum(p, 2, 5), 4 + 1 + 5 + 9);assert.equal(rangeSum(p, 3, 3), 1); // a single element// Brute-force cross-check on every range.for (let i = 0; i < xs.length; i++) { for (let j = i; j < xs.length; j++) { const brute = xs.slice(i, j + 1).reduce((a, b) => a + b, 0); assert.equal(rangeSum(p, i, j), brute); }}console.log("ok");// p[i] = sum of xs[0..i-1]. One extra slot so p[0] = 0 (the empty prefix).static long[] BuildPrefix(int[] xs){ var p = new long[xs.Length + 1]; // long: n values of up to 2^31 can overflow int for (int i = 0; i < xs.Length; i++) p[i + 1] = p[i] + xs[i]; // 1. each prefix extends the previous one return p;}
// Sum of xs[i..j] inclusive, in O(1).static long RangeSum(long[] p, int i, int j) => p[j + 1] - p[i]; // 2. up to j, minus before i
int[] xs = [3, -1, 4, 1, 5, 9, -2, 6];var p = BuildPrefix(xs);if (!p.SequenceEqual([0L, 3, 2, 6, 7, 12, 21, 19, 25])) throw new Exception("bad prefix");if (RangeSum(p, 2, 5) != 19) throw new Exception("expected 19");if (RangeSum(p, 3, 3) != 1) throw new Exception("expected 1");// Brute-force cross-check on every range.for (int i = 0; i < xs.Length; i++) for (int j = i; j < xs.Length; j++) if (RangeSum(p, i, j) != xs[i..(j + 1)].Sum()) throw new Exception($"mismatch at {i}..{j}");Console.WriteLine("ok");2-D prefix sums
Section titled “2-D prefix sums”Same idea on a grid. S[r][c] is the sum of the rectangle from the top-left corner down to (but not including) row r and column c. Building and querying both use inclusion-exclusion: add the two overlapping strips, then fix the corner you counted twice.
S[r+1][c+1] = grid[r][c] + S[r][c+1] + S[r+1][c] − S[r][c].sum(r1..r2, c1..c2) = S[r2+1][c2+1] − S[r1][c2+1] − S[r2+1][c1] + S[r1][c1].
def build_2d(grid: list[list[int]]) -> list[list[int]]: """s[r][c] = sum of the rectangle grid[0..r-1][0..c-1] (a summed-area table).""" rows, cols = len(grid), len(grid[0]) if grid else 0 s = [[0] * (cols + 1) for _ in range(rows + 1)] for r in range(rows): for c in range(cols): # 1. cell + area above + area left - overlap (counted twice) s[r + 1][c + 1] = grid[r][c] + s[r][c + 1] + s[r + 1][c] - s[r][c] return s
def rect_sum(s: list[list[int]], r1: int, c1: int, r2: int, c2: int) -> int: """Sum of grid[r1..r2][c1..c2] inclusive, in O(1).""" # 2. big rectangle - strip above - strip left + corner (subtracted twice) return s[r2 + 1][c2 + 1] - s[r1][c2 + 1] - s[r2 + 1][c1] + s[r1][c1]
if __name__ == "__main__": grid = [ [3, 0, 1, 4], [5, 6, 3, 2], [1, 2, 0, 1], ] s = build_2d(grid) assert rect_sum(s, 0, 0, 2, 3) == 28 # whole grid assert rect_sum(s, 1, 1, 2, 2) == 6 + 3 + 2 + 0 assert rect_sum(s, 2, 3, 2, 3) == 1 # one cell print("ok")import assert from "node:assert/strict";
/** s[r][c] = sum of the rectangle grid[0..r-1][0..c-1] (a summed-area table). */export function build2d(grid: number[][]): number[][] { const rows = grid.length; const cols = rows ? grid[0].length : 0; const s = Array.from({ length: rows + 1 }, () => new Array<number>(cols + 1).fill(0)); for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { // 1. cell + area above + area left - overlap (counted twice) s[r + 1][c + 1] = grid[r][c] + s[r][c + 1] + s[r + 1][c] - s[r][c]; } } return s;}
/** Sum of grid[r1..r2][c1..c2] inclusive, in O(1). */export function rectSum(s: number[][], r1: number, c1: number, r2: number, c2: number): number { // 2. big rectangle - strip above - strip left + corner (subtracted twice) return s[r2 + 1][c2 + 1] - s[r1][c2 + 1] - s[r2 + 1][c1] + s[r1][c1];}
const grid = [ [3, 0, 1, 4], [5, 6, 3, 2], [1, 2, 0, 1],];const s = build2d(grid);assert.equal(rectSum(s, 0, 0, 2, 3), 28); // whole gridassert.equal(rectSum(s, 1, 1, 2, 2), 6 + 3 + 2 + 0);assert.equal(rectSum(s, 2, 3, 2, 3), 1); // one cellconsole.log("ok");int[,] grid = { { 3, 0, 1, 4 }, { 5, 6, 3, 2 }, { 1, 2, 0, 1 } };var s = Build2D(grid);if (RectSum(s, 0, 0, 2, 3) != 28) throw new Exception("whole grid");if (RectSum(s, 1, 1, 2, 2) != 11) throw new Exception("inner block");if (RectSum(s, 2, 3, 2, 3) != 1) throw new Exception("one cell");Console.WriteLine("ok");
// s[r,c] = sum of the rectangle grid[0..r-1, 0..c-1] (a summed-area table).static long[,] Build2D(int[,] grid){ int rows = grid.GetLength(0), cols = grid.GetLength(1); var s = new long[rows + 1, cols + 1]; for (int r = 0; r < rows; r++) for (int c = 0; c < cols; c++) // 1. cell + area above + area left - overlap (counted twice) s[r + 1, c + 1] = grid[r, c] + s[r, c + 1] + s[r + 1, c] - s[r, c]; return s;}
// Sum of grid[r1..r2, c1..c2] inclusive, in O(1).static long RectSum(long[,] s, int r1, int c1, int r2, int c2) => // 2. big rectangle - strip above - strip left + corner (subtracted twice) s[r2 + 1, c2 + 1] - s[r1, c2 + 1] - s[r2 + 1, c1] + s[r1, c1];Difference arrays
Section titled “Difference arrays”Use this when updates come first and reads come after (“offline” updates).
- For each update
(lo, hi, d):diff[lo] += d. The effect switches on atlo. - And
diff[hi + 1] −= d. It switches off just afterhi. Sizediffas n + 1 sohi + 1is always in bounds. - Rebuild with one running sum:
value[i] = diff[0] + … + diff[i].
def apply_range_adds(n: int, updates: list[tuple[int, int, int]]) -> list[int]: """Start from n zeros; each (lo, hi, delta) adds delta to every index in [lo..hi]. O(n + k) instead of O(n * k).""" diff = [0] * (n + 1) # one spare slot so hi + 1 never overflows the array for lo, hi, delta in updates: diff[lo] += delta # 1. the effect starts at lo... diff[hi + 1] -= delta # 2. ...and is cancelled just after hi out = [] running = 0 for i in range(n): running += diff[i] # 3. a prefix sum of the diffs rebuilds the values out.append(running) return out
if __name__ == "__main__": assert apply_range_adds(6, [(1, 3, 2), (2, 5, 3), (0, 0, -1)]) == [-1, 2, 5, 5, 3, 3] assert apply_range_adds(3, []) == [0, 0, 0] # Cross-check against the slow way. ups = [(0, 4, 1), (2, 2, 7), (3, 4, -2)] slow = [0] * 5 for lo, hi, d in ups: for i in range(lo, hi + 1): slow[i] += d assert apply_range_adds(5, ups) == slow print("ok")import assert from "node:assert/strict";
type Update = [lo: number, hi: number, delta: number];
/** Start from n zeros; each [lo, hi, delta] adds delta to every index in [lo..hi]. * O(n + k) instead of O(n * k). */export function applyRangeAdds(n: number, updates: Update[]): number[] { const diff = new Array<number>(n + 1).fill(0); // spare slot so hi + 1 stays in bounds for (const [lo, hi, delta] of updates) { diff[lo] += delta; // 1. the effect starts at lo... diff[hi + 1] -= delta; // 2. ...and is cancelled just after hi } const out: number[] = []; let running = 0; for (let i = 0; i < n; i++) { running += diff[i]; // 3. a prefix sum of the diffs rebuilds the values out.push(running); } return out;}
assert.deepEqual(applyRangeAdds(6, [[1, 3, 2], [2, 5, 3], [0, 0, -1]]), [-1, 2, 5, 5, 3, 3]);assert.deepEqual(applyRangeAdds(3, []), [0, 0, 0]);// Cross-check against the slow way.const ups: Update[] = [[0, 4, 1], [2, 2, 7], [3, 4, -2]];const slow = new Array<number>(5).fill(0);for (const [lo, hi, d] of ups) for (let i = lo; i <= hi; i++) slow[i] += d;assert.deepEqual(applyRangeAdds(5, ups), slow);console.log("ok");// Start from n zeros; each (lo, hi, delta) adds delta to every index in [lo..hi].// O(n + k) instead of O(n * k).static long[] ApplyRangeAdds(int n, (int Lo, int Hi, int Delta)[] updates){ var diff = new long[n + 1]; // spare slot so Hi + 1 stays in bounds foreach (var (lo, hi, delta) in updates) { diff[lo] += delta; // 1. the effect starts at lo... diff[hi + 1] -= delta; // 2. ...and is cancelled just after hi } var output = new long[n]; long running = 0; for (int i = 0; i < n; i++) { running += diff[i]; // 3. a prefix sum of the diffs rebuilds the values output[i] = running; } return output;}
var got = ApplyRangeAdds(6, [(1, 3, 2), (2, 5, 3), (0, 0, -1)]);if (!got.SequenceEqual([-1L, 2, 5, 5, 3, 3])) throw new Exception(string.Join(",", got));if (!ApplyRangeAdds(3, []).SequenceEqual([0L, 0, 0])) throw new Exception("empty updates");Console.WriteLine("ok");Try it. Each update touches exactly two cells of diff, whether the range is one cell or all of them:
Prefix sums plus a hash map (the idea, in words)
Section titled “Prefix sums plus a hash map (the idea, in words)”A range sum is a difference of two prefixes: sum(i..j) = P[j+1] − P[i]. So “is there a range ending at j with sum k?” becomes “have I already seen a prefix equal to P[j+1] − k?”. That is a hash-map lookup, O(1). Scan left to right, keep a map from prefix value to whatever you need about it (how often it occurred, where it first occurred), and query before you insert.
This is the tool when values can be negative, which breaks the sliding window. Remember to seed the map with the empty prefix (0), which plays the same role as P[0].
What it costs
Section titled “What it costs”| Build | Per query | Per update | Space | |
|---|---|---|---|---|
| Re-sum the range each time | none | O(n) | O(1) | O(1) |
| Prefix array | O(n) | O(1) | O(n) (rebuild) | O(n) |
| 2-D prefix array (R × C) | O(R·C) | O(1) | O(R·C) | O(R·C) |
| Difference array, k updates | O(n) | read at end | O(1) | O(n) |
| Fenwick / segment tree | O(n) | O(log n) | O(log n) | O(n) |
The trade-off to say out loud: a prefix array is perfect for static data. Each point update shifts every later prefix, so it costs O(n). If updates and queries are interleaved, reach for a Fenwick tree or segment tree, which make both O(log n).
With q queries, brute force is O(n·q). For n = q = 10⁵ that’s 10¹⁰ operations, far too slow. Prefix sums make it O(n + q) = 2·10⁵.
Variations you’ll meet
Section titled “Variations you’ll meet”- Indicator prefix sums. Prefix-sum
1 if xs[i] is a vowel else 0to answer “how many vowels ins[i..j]” in O(1). One array per category you count. - Prefix XOR / product / min? XOR works exactly like sum (it is its own inverse:
X[j+1] ^ X[i]). Products work only without zeros (division is the inverse). Min and max do not: there is no way to “subtract” a minimum. Use a sparse table or segment tree for range min. - Prefix and suffix passes. Some problems need “everything to my left” and “everything to my right” for each index. Compute a left-to-right pass and a right-to-left pass, then combine.
- 2-D difference arrays. Add
vto a sub-rectangle with four writes (+v,−v,−v,+vat the corners), then a 2-D prefix sum rebuilds. - Sweep line counting. Sort
(time, +1)and(time, −1)events and run a running total: the maximum is the peak overlap. This is a difference array over sparse coordinates. See sweep line.
Pitfalls
Section titled “Pitfalls”- Off-by-one. With the n + 1 layout, the range
[i..j]isP[j+1] − P[i]. Write that formula down before coding and test it on a one-element range. - Overflow. n values of up to 10⁹ sum to 10¹⁴. Use
longin C#. In TS,numberis exact only up to 2⁵³ ≈ 9·10¹⁵; beyond that usebigint. Python ints don’t overflow. See math. - Mutating the input. Building the prefix array in place (
xs[i] += xs[i-1]) saves memory but destroys the original. Say so if you do it. - Forgetting the empty prefix in the hash-map variant. Without
{0: …}seeded, ranges that start at index 0 are missed. - Difference array bounds.
diff[hi + 1]needs a spare slot, or a guard whenhiis the last index. - Using it with updates. If the array changes between queries, a prefix array silently goes stale.
In the interview
Section titled “In the interview”- Ask: how many queries, and does the array change between them? That decides prefix array vs Fenwick tree vs brute force.
- Ask about negative values before choosing between a sliding window and prefix sums.
- State the definition out loud: “P[k] is the sum of the first k elements, so P[0] is 0 and the range i..j is P[j+1] minus P[i].” It shows you have the off-by-one under control.
- Likely follow-ups: “What if the data is a grid?” (2-D), “What if updates are interleaved with queries?” (Fenwick or segment tree), “Can you do it in O(1) extra space?” (in place, if mutating is allowed).
Check yourself
Section titled “Check yourself”Practice on your own
Section titled “Practice on your own”These are classic textbook exercises, not gym questions:
- Equilibrium index: the index where the sum on the left equals the sum on the right.
- Count the vowels in many substring ranges of one fixed string.
- Given bookings
(first_row, last_row, seats), report seats booked per row. - Sum of a sub-matrix, for many queries on a fixed matrix.
- Maximum number of overlapping intervals at any point (difference array over the timeline).
Go deeper
Section titled “Go deeper”- cp-algorithms: Fenwick tree, for when prefix sums need updates too, including range-update tricks built on difference arrays.
- Competitive Programmer’s Handbook (Laaksonen), ch. 9 “Range queries”: prefix sums, 2-D prefix sums and sparse tables.
- USACO Guide: Prefix Sums and More on Prefix Sums: 2-D sums and difference arrays with worked problems.