Prefix Sums
Precompute running totals once, then get the sum of any range with one subtraction. Plus the prefix-sum and hash map trick for counting runs.
On this page
A small café records its takings each day: 5, 2, 6, 3, 1, 4. The owner keeps asking questions. How much from day 3 to day 5? From day 1 to day 4? Day 2 to day 6? Each answer means adding up a stretch of numbers again, and the list of days grows every week.
A car's odometer has the answer. To know how far you drove between two towns, you do not add up every metre of road. You read the odometer at each town and subtract. Keep a running total of the takings, and every "how much between here and there?" becomes one subtraction.
The idea: running totals, then subtract
Build a second array, prefix, where prefix[i] is the sum of the first i items. It starts with prefix[0] = 0, the sum of no items at all, so it is one longer than the input.
Number the days from 0, as an array does, so days 3 to 5 are items 2 to 4. The sum of items from index l to index r, both included, is everything up to r minus everything before l:
sum(l..r) = prefix[r + 1] − prefix[l]
prefix[0] = 0for i from 0 to n - 1: prefix[i + 1] = prefix[i] + nums[i]sum(l..r) = prefix[r + 1] - prefix[l]- nums051226334154prefix00123456
1
prefix[0]is 0: before the first day, nothing has been taken. The prefix array has 7 slots for 6 days. - nums05i1226334154prefix001523456
2
prefix[1]= 0 + 5 = 5: the total of the first day. - nums0512i26334154prefix0015273456
3
prefix[2]= 5 + 2 = 7. - nums051226i334154prefix001527313456
4
prefix[3]= 7 + 6 = 13. - nums05122633i4154prefix00152731341656
5
prefix[4]= 13 + 3 = 16. - nums0512263341i54prefix0015273134165176
6
prefix[5]= 16 + 1 = 17. - nums051226334154iprefix001527313416517621
7
prefix[6]= 17 + 4 = 21, the total of all six days. Building took one pass. - nums051226l3341r54prefix001527313416517621
8The owner asks for days 3 to 5, which are items 2 to 4. That is
prefix[5] − prefix[2]= 17 − 7 = 10. Check: 6 + 3 + 1 = 10. - nums051226334154prefix001527313416517621
- days 1 to 4
- 16
- days 2 to 6
- 16
9Every question now costs one subtraction, however long the range. Days 1 to 4 (items 0 to 3): 16 − 0 = 16. Days 2 to 6 (items 1 to 5): 21 − 5 = 16.
function buildPrefix(nums) {
const prefix = [0];
for (let i = 0; i < nums.length; i++) {
prefix.push(prefix[i] + nums[i]);
}
return prefix;
}
// Sum of nums[l..r], both ends included.
function rangeSum(prefix, l, r) {
return prefix[r + 1] - prefix[l];
}
const prefix = buildPrefix([5, 2, 6, 3, 1, 4]); // [0, 5, 7, 13, 16, 17, 21]
rangeSum(prefix, 2, 4); // 10def build_prefix(nums):
prefix = [0]
for i in range(len(nums)):
prefix.append(prefix[i] + nums[i])
return prefix
# Sum of nums[l..r], both ends included.
def range_sum(prefix, l, r):
return prefix[r + 1] - prefix[l]
prefix = build_prefix([5, 2, 6, 3, 1, 4]) # [0, 5, 7, 13, 16, 17, 21]
range_sum(prefix, 2, 4) # 10Counting runs with a given total
Prefix sums get stronger next to a hash map. Take a list of daily profits and losses, [2, −1, 3, −2, 2], and ask: how many runs of days in a row add up to exactly 2?
Checking every run is O(n²). A sliding window cannot help, because the losses are negative. But the formula above says a run ending here sums to 2 exactly when an earlier running total equals current running total − 2. So walk once, keep the running total, and keep a map of how many times each running total has appeared. At each day, look up how many earlier totals would make a run of 2.
seen = {0: 1}, running = 0, count = 0for x in nums: running += x count += seen[running - k] (0 if missing) seen[running] += 1- nums021-1233-242
- k
- 2
- running
- 0
- seen
- {0: 1}
- count
- 0
1Before any day, the running total 0 has appeared once. This lets runs that start at day 0 be counted.
- nums02i1-1233-242
- k
- 2
- running
- 2
- need
- 0
- seen
- {0: 1, 2: 1}
- count
- 1
2Running total 2. We need an earlier total of 2 − 2 = 0. It appeared once, so one run ends here: [2].
- nums021-1i233-242
- k
- 2
- running
- 1
- need
- -1
- seen
- {0: 1, 2: 1, 1: 1}
- count
- 1
3Running total 1. We need −1. Never seen, so no run ends here.
- nums021-123i3-242
- k
- 2
- running
- 4
- need
- 2
- seen
- {0: 1, 2: 1, 1: 1, 4: 1}
- count
- 2
4Running total 4. We need 2, seen once, after the first item. So the run that starts after it sums to 2: [−1, 3].
- nums021-1233-2i42
- k
- 2
- running
- 2
- need
- 0
- seen
- {0: 1, 2: 2, 1: 1, 4: 1}
- count
- 3
5Running total 2. We need 0, seen once: the run [2, −1, 3, −2] sums to 2.
- nums021-1233-242i
- k
- 2
- running
- 4
- need
- 2
- seen
- {0: 1, 2: 2, 1: 1, 4: 2}
- count
- 5
6Running total 4. We need 2, seen twice, so two runs end here: [−1, 3, −2, 2] and [2].
- nums021-1233-242
- count
- 5
- all runs
- 15
75 runs add up to 2, found in one pass with 5 lookups. Checking all 15 runs would also work, but grows as n².
function countRunsWithSum(nums, k) {
const seen = new Map([[0, 1]]); // the empty start
let running = 0;
let count = 0;
for (const x of nums) {
running += x;
count += seen.get(running - k) ?? 0;
seen.set(running, (seen.get(running) ?? 0) + 1);
}
return count;
}
countRunsWithSum([2, -1, 3, -2, 2], 2); // 5def count_runs_with_sum(nums, k):
seen = {0: 1} # the empty start
running = 0
count = 0
for x in nums:
running += x
count += seen.get(running - k, 0)
seen[running] = seen.get(running, 0) + 1
return count
count_runs_with_sum([2, -1, 3, -2, 2], 2) # 5| Approach | Time | Space | Why |
|---|---|---|---|
| Add up each range from scratch | O(n) per query | O(1) | q queries cost O(n · q). |
| Prefix sums | O(n) build, O(1) per query | O(n) | q queries cost O(n + q). |
| Count runs with total k | O(n) | O(n) | One pass, one map lookup and one update per item. |
Common mistakes
Check yourself
With prefix = [0, 5, 7, 13, 16, 17, 21], what is the sum of items 1 to 3?
An array has 100,000 numbers and you must answer 10,000 range-sum questions. Roughly how much work does each approach take?
Why does counting runs with total k use prefix sums and a map instead of a sliding window?
What to practise next
Solve Matching Receipts if you have not yet. It uses the same move as the run counter: for each item, look up in a map the partner it needs. Then Linked Lists trade the array's instant indexing for cheap inserts in the middle.