Prefix Sums & Difference Arrays
Prefix sums precompute the running total once so that any range-sum query is a single subtraction, , in . A hash map of prefix frequencies then counts subarrays summing to in — even with negative entries, where the sliding window fails.
╌╌╌╌
This builds on Two Pointers & Sliding Windows. There, the sliding window optimized over contiguous subarrays, but only when feasibility was monotone in width — a sum that grows as the window widens, which needs every entry to be positive. The moment negative numbers enter, that monotonicity is gone: widening a window can lower its sum, so shrinking from the left no longer safely tightens it. This lesson supplies a technique that works with negative entries. Prefix sums restate a subarray's sum as the difference of two precomputed running totals, and that reframing answers range-sum queries in and counts exact-sum subarrays in regardless of sign.
Prefix sums
The sliding window assumed we could update a sum incrementally. Prefix sums generalize this to answer any range-sum query in after a linear precompute. Define
so has length and is built in one pass: . Then the sum of any subarray telescopes:
The conventions here are chosen to kill off-by-one bugs, which are this technique's only real hazard. has length , one entry per boundary between elements rather than per element; is the sum of the first elements, exclusive of . The sentinel makes work with no special case for ranges that touch the left edge, and an empty range returns for free. When a range-sum looks off by one element, the fix is almost always at the boundary: check whether your convention is inclusive or exclusive at each end before touching anything else.
The highlighted entries are and ; their difference is exactly . One subtraction, no rescanning.
To fix the whole construction in one trace, build for from left to right, then answer two queries:
- Build: ; ; ; ; ; ; . One addition per boundary, total.
- Query : , matching .
- Query (the whole array): , matching . Because , the left-edge case needs no branch.
Counting subarrays with sum . Prefix sums turn a subarray scan into for Subarray Sum Equals K. A subarray has sum iff , i.e. . So as we sweep a running prefix left to right, the number of valid left endpoints is the number of earlier prefixes equal to . Keep a hash map of prefix-value frequencies seen so far:
- 1
- 2empty prefix seen once
- 3for to do
- 4
- 50 if absent
- 6
- 7return
Seeding freq with accounts for subarrays that start at index
(those need ). One pass, time and space. Watch it on
with :
- : prefix ; look up , absent, count stays ; record .
- : prefix ; look up , which the seed holds once — count (the subarray ); record .
- : prefix ; look up , present once — count (the subarray ); done.
The map after the sweep is , and the two hits found the two subarrays and , both summing to .
Two pitfalls hide in the bookkeeping. Dropping the seed silently loses every subarray that starts at index . And the lookup must happen before the insert: inserting first lets the current prefix match itself, which counts the empty subarray whenever . The map stores counts, not mere presence, because distinct left endpoints can share a prefix value (any zero-sum stretch creates repeats), and each one is a separate subarray.
This works for any integers, positive or negative, unlike the sliding window, which needed positivity for monotonicity. A concrete case where the window fails but prefixes do not: on with , the subarray sums to zero, yet no positive-only window argument can find it. The prefix sweep sees and, at where the running prefix returns to , looks up in the map, which the seed already holds once — scoring the hit. Restating the subarray condition as a relation between two prefix values, then sweeping once while remembering the prefixes already seen, is the standard fallback when negative entries break a window argument.
Difference arrays, the dual
To apply many range updates "add to every " and only then read the array, invert the relationship. Keep a difference array and for each update do and , two touches. A single prefix-sum pass over at the end materializes the final array, since . Thus range-adds cost instead of . Give length so the poke stays in bounds when a range runs to the last element; the extra entry is never read back. The scheme's one limitation is batching: a query between updates forces a full sweep, and interleaved updates and queries call for a Fenwick tree instead.
The mechanism is worth tracing on two overlapping updates. Start with and . Apply "add to ": , , giving . Apply "add to ": , , giving . Now one prefix-sum sweep of produces — position and received both updates (), position received only the second, and the two pokes per update never touched the interior cells at all. Two range-adds, four pokes, one sweep, versus the six writes a direct loop would make.
Two dimensions
Prefix sums extend to two dimensions as well: precompute , so for a grid point is the sum of the whole block between the origin (top-left) and . Any axis-aligned rectangle sum is then recovered by inclusion–exclusion with four lookups: with corners as in the figure, covers the query plus a strip above it and a strip to its left; subtracting removes the strip above, subtracting removes the strip to the left, and both subtractions remove the corner block twice, so is added back once. The table itself is built in one pass by the same identity read in reverse: .
Beyond the sum: other prefix aggregates
The subtraction trick works because addition has an inverse: to remove the contribution of a prefix, subtract it. Any aggregate whose combine operation is a group — has an identity and inverses — supports the same range query. Two show up constantly.
Prefix XOR. Bitwise xor is its own inverse (), so with the xor of any range is . Counting subarrays with xor equal to is then the exact analogue of the sum count: sweep the running prefix-xor, and at each step look up how many earlier prefixes equal (since ). Same hash map, same .
Prefix products. With no zeros, a prefix product supports range products by division, but the useful trick sidesteps division entirely: the product of all elements except self is , two passes and no division, robust to zeros.
What does not carry over is min and max: they have no inverse, so a prefix-max array cannot answer an arbitrary range's maximum by two lookups. Range min/max over a static array needs a different structure — a sparse table for queries, or the monotonic deque of the previous lesson for a sliding window. The dividing line comes down to invertibility: sum, xor, and count are invertible and yield range queries off a prefix array; min and max are not.
From summed-area tables to Fenwick trees
The four-corner inclusion–exclusion that reads a rectangle sum off a 2-D prefix table is the discrete cousin of the summed-area table introduced to computer graphics by Crow (Summed-Area Tables for Texture Mapping, SIGGRAPH 1984): a preprocessed image where any axis-aligned box's average brightness is four lookups, used for fast texture filtering. The same table, under the name integral image, is what makes the Viola–Jones face detector (Viola & Jones, Rapid Object Detection Using a Boosted Cascade of Simple Features, CVPR 2001) evaluate its rectangular Haar features in constant time per feature — the technique behind the first practical real-time face detector, and still the textbook example of trading preprocessing for queries.
Prefix sums are also the point where this static idea meets its dynamic
successor. The moment updates interleave with queries, a plain prefix array must
be rebuilt on every write, each; the Fenwick (binary indexed) tree of
Fenwick (A New Data Structure for Cumulative Frequency Tables, Software:
Practice and Experience, 1994) keeps prefix sums under point updates in
per operation, and the segment tree
generalizes further to range updates and non-sum aggregates. The difference-array
trick in this lesson is the special case that makes Fenwick trees support
range updates: a Fenwick tree over the difference array is the standard
range-update, point-query
structure.
Finally, the prefix-map trick for counting subarrays with a given sum is one instance of a broad pattern: canonicalize a subarray's answer as a function of two prefix states, then hash the prefix states. The same move counts subarrays with sum divisible by (hash the running prefix modulo ), subarrays with equal counts of two symbols (hash a running difference), and the longest such subarray (store the first index each prefix value appeared). Recognizing that a constraint on a range is really a relation between its two endpoints is the reusable idea underneath all of them.
Takeaways
- Prefix sums answer any range sum as in after an precompute; the sentinel and boundary-not-element convention are what keep the off-by-one bugs away.
- A hash map of prefix frequencies counts subarrays with sum in , and — unlike the sliding window — works with negative entries, because it restates the constraint as a relation between two prefix values.
- The difference-array dual applies range-adds in : two pokes per update, one prefix-sweep to materialize.
- Prefix sums generalize to 2-D rectangle queries in by four-corner inclusion–exclusion, the summed-area / integral-image trick.
- Once updates and queries interleave, a static prefix array no longer suffices; upgrade to a Fenwick or segment tree.
╌╌ END ╌╌