Prefix Sums
Quick reference
| Build the prefix (or prefix-product) array | O(n) |
| Range query, after the array is built | O(1) |
| Range query, re-summed from scratch (for comparison) | O(n) per query |
| Difference-array range update | O(1) per update, O(n) to reveal final values |
Imagine an odometer that, at every mile marker along a road, already tells you the total distance driven from the very start. Want to know the distance between mile marker 12 and mile marker 47? You don't redrive the road β you read the odometer at 47, read it at 12, and subtract. A prefix-sum array is exactly that odometer, built once for a list of numbers: precompute the running total up through every position, and afterward, the sum of any stretch [i, j] is just one subtraction away.
This only pays off because the running totals are computed once and reused many times. Building the odometer readings costs one pass over the whole road β O(n). But every question about a stretch of road after that costs a single subtraction β O(1) β instead of re-adding that stretch from scratch every time someone asks.
The prefix array
// prefix[0] = 0 // empty range sums to 0 β padding that avoids
// special-casing "what if the range starts at 0"
// prefix[i] = prefix[i - 1] + arr[i - 1] // running total through the first i original elements
// prefix has length n + 1 for an original array of length n β one extra leading slotbuild the prefix array β O(n)
function buildPrefix(arr):
n = length(arr)
prefix = array of size n + 1
prefix[0] = 0
for i from 1 to n:
prefix[i] = prefix[i - 1] + arr[i - 1]
return prefixrange sum query [i, j] β O(1)
function rangeSum(prefix, i, j):
// sum of arr[i..j] inclusive = everything through j, minus everything before i
return prefix[j + 1] - prefix[i]prefix product β same trick, different operator
function buildPrefixProduct(arr):
n = length(arr)
prefix = array of size n + 1
prefix[0] = 1 // empty product is 1, not 0
for i from 1 to n:
prefix[i] = prefix[i - 1] * arr[i - 1]
return prefixdifference array β O(1) range updates, revealed with a prefix sum
function applyRangeAdd(diff, i, j, value):
diff[i] += value // start adding 'value' from index i onward
diff[j + 1] -= value // cancel that addition from index j + 1 onward
function reveal(diff):
return buildPrefix(diff) // a prefix sum over the diffs turns "start/stop" markers
// back into the real per-index totals, in one O(n) passThe whole point is moving the cost: pay once, up front, to build the array; every query after that is arithmetic, not a walk.
| Operation | Time | Space | Why |
|---|---|---|---|
| Build the prefix (or prefix-product) array | O(n) | O(n) | One pass over the input, carrying a running total (or product) forward one slot at a time. |
| Range query, after the array is built | O(1) | O(1) | A range sum is one subtraction of two already-computed totals β no walking the range itself. |
| Range query, re-summed from scratch (for comparison) | O(n) per query | O(1) | Without a prebuilt prefix array, every query has to add up its own range again β the cost the prefix array exists to eliminate. |
| Difference-array range update | O(1) per update, O(n) to reveal final values | O(n) | Each update only touches two positions (the range's start and one past its end); the real per-index values only get reconstructed once, via a single prefix-sum pass over the diffs. |
Off-by-one on the extra leading zero
The prefix array has length n + 1, not n β prefix[0] = 0 is real padding, not an accident. Forgetting it (or forgetting the "+1" when translating a range [i, j] into prefix[j + 1] - prefix[i]) is the single most common bug in prefix-sum code β it silently shifts every range sum by one element.
Stale prefix sums after the data changes
A prefix array is a snapshot. If arr[5] changes after buildPrefix ran, every prefix[i] for i > 5 is now wrong, and rangeSum will silently return stale answers instead of failing loudly. Either rebuild the whole prefix array after a change (O(n)) or use a structure designed for updates β that's a different tool for a later phase.
Negative numbers break "the answer is just the range"
With all-positive numbers, the biggest-sum subarray is trivially the whole array. With negatives mixed in, that's no longer true, and "which two prefix values, subtracted, give the best result" stops being obvious from position alone β you need to remember which prefix-sum values you've already seen and where, which motivates pairing prefix sums with a fast-lookup structure (the next topic's whole subject) rather than a plain scan.
Sign in to mark problems done β progress syncs across devices.