Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
Prefix Sums & Range Queries  ›  Lesson

Prefix Sums, Difference Arrays & Windows

Prefix Sums & Difference Arrays 30 minAdd everything once so every range question is one subtraction
You're building a piece ofQuarry — the analytics engine
This piece — range_totals(): Answers any 'how many between minute l and r' in O(1) from a prefix row.
Scenario Quarry's dashboard asks how many errors happened between two minutes, thousands of times a second. Rescanning the log for each panel is too slow, so the analytics engine builds one prefix row and answers every panel with a subtraction.
Your task
Build range_totals(counts, queries). counts is the per-minute log and each query is [l, r] with both ends included. Return the total of each query in order, using one prefix row so every query is a single subtraction. Example: range_totals([4, 2, 0, 3, 5, 1], [[1, 4], [0, 0], [0, 5]]) → [10, 4, 15].

Prefix Sums, Difference Arrays & Windows

In short: build a running-total row once, P[i + 1] = P[i] + a[i], and any range total becomes one subtraction, P[r + 1] − P[l]. The spare P[0] = 0 cell means a range starting at minute 0 needs no special case. The same trick runs backwards: a difference array turns a wide range update into two point writes, with one final sweep — itself a prefix sum — turning differences back into values. Both only work because addition can be undone; min has no inverse, which is why the rest of this section exists.

A prefix row P with a spare zero cell; P[1] and P[5] are highlighted and subtracted to total minutes 1..4

From this section on you build Quarry, a search-and-storage engine: the kind of program that sits under a dashboard, keeps a log of everything that happened, and answers questions about it fast. Later sections give it an index, a text search and a storage layer. This section gives it an analytics engine.

The raw material is simple. Quarry keeps one number per minute: how many requests arrived, how many errors, how many bytes were written. A day is 1,440 minutes, a month is 43,200, and the dashboard asks the same shape of question over and over:

How many errors between minute l and minute r?

Every lesson in this section answers that question under a harder rule. Here the log never changes after it is written. Next lesson it gets edited. By the end it takes range updates, minimum queries and lives on disk.

The brute force, and what it costs

The obvious answer is a slice and a sum:

python

Each query reads every minute it covers. One query over a month is 43,200 reads; a dashboard with twenty panels refreshing every few seconds asks thousands of them. That's O(n) per query and O(n·q) in total, and nearly all of it is re-adding numbers that were added a moment ago by the previous query.

The prefix row

Add everything up once, left to right, and keep every running total:

P[0] = 0 and P[i + 1] = P[i] + errors[i].

P[i] is the total of the first i minutes. It has one more cell than the log, and P[0] = 0 stands for "no minutes at all". That spare zero is what makes the next line work without special cases:

total of minutes l..r = P[r + 1] − P[l].

P[r + 1] counts everything up to and including minute r. P[l] counts everything before minute l. Subtracting removes exactly the part you don't want. Watch the row fill, then watch two queries each read just two cells:

watch it move · array

Building the prefix row costs one pass. After that, any range total is two cells and one subtraction, however wide the range.

P[0] = 0 is the total of no minutes. The label under each cell is the minute it adds. 7 cells. At index 0. 1 of 7 cells settled.

1 / 9

Preparation is O(n) time and O(n) extra memory. Every query after that is O(1). For q queries the total drops from O(n·q) to O(n + q).

What does this print?
errors = [3, 1, 4, 1, 5]
P = [0]
for e in errors:
    P.append(P[-1] + e)
print(P)

The off-by-one is the whole difficulty. Try the formula on the smallest range there is:

What does this print?
P = [0, 3, 4, 8, 9, 14]
l, r = 2, 2
print(P[r + 1] - P[l], P[r] - P[l])

Now the same thing as code you can edit. Change errors, or the query, and run it again: the drawing is your code's own run.

your code, drawn · array

Your prefix row, built one cell at a time, then one query answered with two reads.

Here is a teammate's version. It passes on some queries and fails on others:

broken — fix it

Minutes 1..4 hold 2 + 0 + 3 + 5 = 10, but this prints 5. The subtraction stops one minute early.

❓ Cross-question — "Why not store P without the leading zero?" You can, but then every query starting at minute 0 needs an if l == 0 branch, and every reader of your code has to check that branch. The spare cell costs one integer and removes a special case. That trade shows up again and again in this section.

Why subtraction is allowed

The prefix row works because addition can be undone. P[r + 1] is too much by exactly P[l], and subtraction removes it. Any operation with an inverse gets the same trick: products (divide, if no zeros), XOR (XOR undoes itself), counts.

min has no inverse. Knowing the smallest error count in minutes 0..9 and in 0..3 tells you nothing about minutes 4..9: the minimum might sit in the part you're trying to remove. That single fact is why the rest of this section exists. Blocks, Fenwick trees and segment trees are all ways of answering range questions when you can't subtract, or when the log keeps changing.

Difference arrays: the mirror image

Sometimes the questions are cheap and the updates are expensive. A backfill job says "add 5 to every minute from 1 to 3", then "add 2 to minutes 2 to 5", a thousand times, and only at the end does anyone read the result. Doing each update cell by cell is O(width) apiece.

A difference array stores how each minute differs from the one before it. Adding d to minutes l..r changes only two differences: minute l goes up by d relative to its neighbour, and minute r + 1 comes back down by d. So an update is two writes, and one prefix pass at the end rebuilds every value:

watch it move · array

A range update touches two cells of the difference array. One running-sum sweep at the end turns differences back into values.

Add 5 to minutes 1..3. Minute 1 starts 5 higher than minute 0. 7 cells. At index 1.

1 / 10

The sweep is a prefix sum. Prefix sums and difference arrays undo each other, the way integration and differentiation do. Step through the rebuild yourself:

step through it
1diff = [0, 5, 2, 0, -5, 0, -2]
2values = []
3running = 0
4for d in diff[:-1]:
5 running += d
6 values.append(running)
7print(values)

Windows

Section 1's sliding window answered "which 3 back-to-back minutes were busiest?" by adding the minute that enters and subtracting the one that leaves. The prefix row answers it too: the window starting at i of width k totals P[i + k] − P[i].

python

Both are O(n). They differ in what they're good for. The slide uses O(1) memory and visits windows in order, so it suits a live stream. The prefix row costs O(n) memory but answers any window, in any order, as many times as you like. So it suits a dashboard. Section 1's growing-and-shrinking window, meanwhile, needs every count to be non-negative. The prefix row doesn't care about signs, which is what makes this lesson's boss exercise possible.

How many queries before building the row pays off? For a 1,000-minute log, an average rescan reads about half the log, so q queries cost about q·1000/2 cells, while the prefix row costs 1000 + q:

queries q rescanning prefix row prefix row is cheaper by
1 500 1,001 0.5× (it's dearer)
10 5,000 1,010 5×
100 50,000 1,100 45×
250 125,000 1,250 100×
1,000 500,000 2,000 250×

Below a few hundred queries the one-off pass over the log is a large share of the prefix row's cost. Above that the rescans pull away. A dashboard reaches 250 queries in seconds.

Coming from Java/JS: itertools.accumulate(errors, initial=0) builds the prefix row in one call, like a reduce that keeps every intermediate value. The loop is spelled out here so you can see each cell arrive.


Idioms & real-world patterns

Counting ranges with a hash map

"How many ranges of minutes have a net change of exactly t?" A range l..r hits t when P[r + 1] − P[l] = t, so P[l] = P[r + 1] − t. Walk the row once and keep a dict of how many times each prefix total has been seen. Each new prefix looks up its partner in O(1):

python

That's O(n) for a question that looks as if it needs O(n²) pairs, and negatives don't break it.

⚡ Advanced — two dimensions

A heat map of errors per server per minute is a grid. The prefix idea extends: S[i][j] totals the rectangle above and left of cell (i, j), and any rectangle is four reads with inclusion–exclusion. That's the capstone exercise.

Quick practice

What does this print?
P = [0, 2, 5, 5, 9]
l, r = 1, 3
print(P[r + 1] - P[l])
broken — fix it

Adding 5 to minutes 1..3 should leave minute 4 back at 0, but it prints [0, 5, 5, 5, 5] — the bump never ends.


🎯 Your turn

Build Quarry's first piece: range_totals(counts, queries). counts is the per-minute log and each query is [l, r] with both ends included. Return one total per query, in order. Build the prefix row once, and answer every query with one subtraction.

  • range_totals([4, 2, 0, 3, 5, 1], [[1, 4], [0, 0], [0, 5]]) → [10, 4, 15]

Then press ▶ Run, tap the Live App try chips to call it with different inputs, and hit ✓ Check. Green means this piece of Quarry is built. ✅

Practice — level up

0/6 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upThe prefix row

Write prefix_row(counts) returning the prefix row: a list one longer than counts whose cell i is the total of the first i counts. Cell 0 is always 0. prefix_row([4, 2, 0, 3]) → [0, 4, 6, 6, 9].

prefix-sumarrays
DrillThe busiest stretch

Write busiest_window(counts, k) returning [start, total] for the k back-to-back minutes with the highest total. On a tie, the earliest start wins. 1 <= k <= len(counts). busiest_window([4, 2, 0, 3, 5, 1, 6, 2], 3) → [4, 12].

prefix-sumsliding-window
BuildBackfill in two writes

Write apply_bumps(n, bumps). The log has n minutes, all 0. Each bump is [l, r, d]: add d to every minute from l to r (both included). Return the final log. Use a difference array so that every bump is two writes, then one sweep. apply_bumps(6, [[1, 3, 5], [2, 5, 2]]) → [0, 5, 7, 7, 2, 2].

difference-arrayprefix-sum
BossRuns that net to a target

Interview-style. Quarry logs the net change in open connections per minute, so values can be negative. Write count_target_runs(changes, target) returning how many contiguous, non-empty runs of minutes have a total of exactly target.

Constraints: 1 <= len(changes) <= 10^5, each value and target between -10^4 and 10^4. Aim for O(n). The tests are small, but an O(n²) answer would be far too slow at the stated size.

Examples:

  • count_target_runs([1, 1, 1], 2) → 2 (minutes 0–1 and 1–2)
  • count_target_runs([3, -3, 3, -3], 0) → 4
  • count_target_runs([5], 3) → 0
prefix-sumhash-map
CapstoneThe heat map

Write grid_totals(grid, queries). grid[i][j] is the error count of server i in minute j. Each query is [r1, c1, r2, c2], a rectangle with both corners included. Return one total per query, reading four cells of a 2-D prefix table per query. grid_totals([[1, 2], [3, 4]], [[0, 0, 1, 1], [1, 0, 1, 1]]) → [10, 7].

2d-prefix-sumprefix-sumrange-sum
ExplainWhy an edited log breaks the prefix row

Quarry's prefix row answers range totals in O(1). Then a late report arrives and minute 3 of a 43,200-minute log has to change. Explain what that single edit costs the prefix row, why a difference array doesn't rescue you when queries and edits are mixed, and what property a better structure would need.

range_totals.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
range_totals(counts, queries) → listThe total of each [l, r] range of minutes, both ends included.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.