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.

15 minLesson 6 of 6
On this page
  1. The idea: running totals, then subtract
  2. Counting runs with a given total
  3. Common mistakes
  4. Check yourself
  5. What to practise next

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]

0 1 2 3 4 5 5 2 6 3 1 4 prefix[5] = 5 + 2 + 6 + 3 + 1 = 17 prefix[2] = 7 17 − 7 = 10 = 6 + 3 + 1
prefix[5] covers items 0 to 4. Subtract prefix[2], which covers items 0 and 1, and items 2 to 4 are left.
Build the running totals, then answer a questionUse the arrows or ← → keys
prefix[0] = 0for i from 0 to n - 1:  prefix[i + 1] = prefix[i] + nums[i]sum(l..r) = prefix[r + 1] - prefix[l]
  1. nums
    05
    12
    26
    33
    41
    54
    prefix
    00
    1
    2
    3
    4
    5
    6

    1prefix[0] is 0: before the first day, nothing has been taken. The prefix array has 7 slots for 6 days.

  2. nums
    05i
    12
    26
    33
    41
    54
    prefix
    00
    15
    2
    3
    4
    5
    6

    2prefix[1] = 0 + 5 = 5: the total of the first day.

  3. nums
    05
    12i
    26
    33
    41
    54
    prefix
    00
    15
    27
    3
    4
    5
    6

    3prefix[2] = 5 + 2 = 7.

  4. nums
    05
    12
    26i
    33
    41
    54
    prefix
    00
    15
    27
    313
    4
    5
    6

    4prefix[3] = 7 + 6 = 13.

  5. nums
    05
    12
    26
    33i
    41
    54
    prefix
    00
    15
    27
    313
    416
    5
    6

    5prefix[4] = 13 + 3 = 16.

  6. nums
    05
    12
    26
    33
    41i
    54
    prefix
    00
    15
    27
    313
    416
    517
    6

    6prefix[5] = 16 + 1 = 17.

  7. nums
    05
    12
    26
    33
    41
    54i
    prefix
    00
    15
    27
    313
    416
    517
    621

    7prefix[6] = 17 + 4 = 21, the total of all six days. Building took one pass.

  8. nums
    05
    12
    26l
    33
    41r
    54
    prefix
    00
    15
    27
    313
    416
    517
    621

    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.

  9. nums
    05
    12
    26
    33
    41
    54
    prefix
    00
    15
    27
    313
    416
    517
    621
    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.

1 / 9
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); // 10
def 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)  # 10

Counting 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.

How many runs add up to 2?Use the arrows or ← → keys
seen = {0: 1}, running = 0, count = 0for x in nums:  running += x  count += seen[running - k]  (0 if missing)  seen[running] += 1
  1. nums
    02
    1-1
    23
    3-2
    42
    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.

  2. nums
    02i
    1-1
    23
    3-2
    42
    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].

  3. nums
    02
    1-1i
    23
    3-2
    42
    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.

  4. nums
    02
    1-1
    23i
    3-2
    42
    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].

  5. nums
    02
    1-1
    23
    3-2i
    42
    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.

  6. nums
    02
    1-1
    23
    3-2
    42i
    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].

  7. nums
    02
    1-1
    23
    3-2
    42
    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².

1 / 7
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); // 5
def 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
ApproachTimeSpaceWhy
Add up each range from scratchO(n) per queryO(1)q queries cost O(n · q).
Prefix sumsO(n) build, O(1) per queryO(n)q queries cost O(n + q).
Count runs with total kO(n)O(n)One pass, one map lookup and one update per item.

Common mistakes

Check yourself

Quick check

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.