Beginner: foundations · Arrays and strings · topic 7 of 61 · 35 min
Prefix sums and difference arrays
Explanation
The problem: many range questions
Suppose a shop has daily sales for a year and the manager asks many questions like "what were total sales from day 40 to day 75?". Adding the days each time costs O(n) per question, so q questions cost O(n × q). With a year of data and thousands of questions, that adds up fast.
Prefix sums
A prefix is the start of a list. The prefix sum array holds, at position i, the sum of the first i items. It has n + 1 entries: prefix[0] = 0 (the sum of no items), prefix[1] = nums[0], prefix[2] = nums[0] + nums[1], and so on. Building it takes one pass, O(n) time and O(n) space.
The sum of nums[l] to nums[r] (both included) is then prefix[r + 1] − prefix[l]: the total up to r, minus the total before l. That is O(1) per question.
| i | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| nums[i] | 3 | 1 | 4 | 1 | 5 | — |
| prefix[i] | 0 | 3 | 4 | 8 | 9 | 14 |
Example: the sum of nums[1] to nums[3] is prefix[4] − prefix[1] = 9 − 3 = 6, which matches 1 + 4 + 1.
from itertools import accumulate
nums = [3, 1, 4, 1, 5]
prefix = list(accumulate(nums, initial=0))
print(prefix) # Output: [0, 3, 4, 8, 9, 14]
print(prefix[3 + 1] - prefix[1]) # Output: 6Preview: counting subarrays that sum to k
A subarray is a run of neighbouring items. A subarray from l to r sums to k exactly when prefix[r + 1] − prefix[l] = k, that is, when an earlier prefix equals the current prefix minus k. Keep a dictionary that counts how often each prefix has appeared, and you can count all such subarrays in one O(n) pass. Unlike the sliding window of Module 3, this works with negative numbers too.
def count_subarrays(nums, k):
seen = {0: 1} # the empty prefix
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
print(count_subarrays([1, 2, 3], 3)) # Output: 2 ([1, 2] and [3])
print(count_subarrays([1, -1, 0], 0)) # Output: 3Difference arrays: many range updates
Now flip the problem: many updates, then one read. "Add v to every item from l to r" done directly costs O(r − l + 1) per update. A difference array diff of length n + 1 records only where a change starts and stops: diff[l] += v and diff[r + 1] −= v. After all updates, a running sum over diff gives the final value of every position. Each update is O(1), the final pass is O(n).
Pivot index
The pivot index is where the sum of items to the left equals the sum to the right. With total = sum(nums), walk once and keep left_sum; at index i the right sum is total − left_sum − nums[i]. One pass, O(n) time and O(1) extra space.
Common mistakes
- Off-by-one with the extra 0: the sum of l..r is prefix[r + 1] − prefix[l], not prefix[r] − prefix[l]. Fix: test with l = r and with l = 0.
- Making the difference array length n instead of n + 1, so diff[r + 1] fails for the last index. Fix: allocate n + 1 and ignore the last slot.
- Rebuilding the prefix array inside the query loop. Fix: build it once before answering any query.
- Forgetting seen = {0: 1} in the subarray-sum pattern, which misses subarrays that start at index 0. Fix: the empty prefix counts once.
- Using a sliding window for "sum equals k" when numbers can be negative. Fix: use prefix sums with a dictionary.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Prefix sums and difference arrays · 1/8
This lesson teaches prefix sums and difference arrays, two small tricks that turn slow range work into constant time.
Prefer a video by a teacher? Find free videos on “prefix sum array difference array range update explained” on YouTube ↗
In real life: Seat counts on a long-distance train
A railway reservation system must know how many seats are booked between every pair of stations on a route, because one seat can be sold Delhi–Agra and again Agra–Bhopal. If each booking adds 1 to every station segment it covers, a long route with many bookings becomes slow. With a difference array, each booking is two updates (where the journey starts and just after it ends), and one pass gives the occupancy of every segment. This is a simplified picture of the idea; real systems add many more rules.
stations = 6
bookings = [(0, 2, 3), (1, 4, 2), (3, 5, 4)] # (from, to, seats), both included
diff = [0] * (stations + 1)
for l, r, seats in bookings:
diff[l] += seats
diff[r + 1] -= seats
running, taken = 0, []
for i in range(stations):
running += diff[i]
taken.append(running)
print(taken)Practice (0/4 done)
Write build_prefix(nums) that returns a list of length len(nums) + 1 where position i holds the sum of the first i items. Position 0 is 0. Example: build_prefix([3, 1, 4]) returns [0, 3, 4, 8].
Write range_sums(nums, queries). Each query is a tuple (l, r) of 0-based indexes, both included, with 0 ≤ l ≤ r < len(nums). Return a list with the sum of nums[l..r] for each query, in order. Build a prefix array once so the total cost is O(n + q). Example: range_sums([3, 1, 4, 1, 5], [(0, 4), (1, 3)]) returns [14, 6].
A train route has n stations, numbered 0 to n − 1, and every station starts with 0 seats booked. Each update (l, r, v) adds v seats to every station from l to r (both included); v can be negative for a cancellation. Write apply_increments(n, updates) that returns the final list of n values. Use a difference array so each update is O(1). Example: apply_increments(5, [(1, 3, 2)]) returns [0, 2, 2, 2, 0].
Write pivot_index(nums) that returns the LEFTMOST index i where the sum of the items left of i equals the sum of the items right of i (nums[i] itself is in neither). If there is none, return -1. O(n) time, O(1) extra space. Example: pivot_index([1, 7, 3, 6, 5, 6]) returns 3.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Strings: building, comparing and countingArrays and strings · same module
- Maximum subarray: Kadane's algorithmArrays and strings · same module
- Counting and grouping with hashingHashing, two pointers and sliding window
- Two pointersHashing, two pointers and sliding window
- Sliding window: variable sizeHashing, two pointers and sliding window
- Lists: many values in one namePython Programming · Lists, tuples, dictionaries and sets
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
nums = [2, 5, 1, 4]. What is the prefix array (with a 0 in front)?
Answer: [0, 2, 7, 8, 12]
Running totals with 0 first: 0, 2, 2+5 = 7, 7+1 = 8, 8+4 = 12.
With prefix of length n + 1, which gives the sum of nums[l..r] inclusive?
Answer: prefix[r + 1] − prefix[l]
prefix[r + 1] covers items 0..r, and prefix[l] covers items 0..l − 1, which we remove.
n items and q range-sum queries. What is the total cost with a prefix array?
Answer: O(n + q)
O(n) to build once, then O(1) per query.
To add v to every item from l to r with a difference array, you do…
Answer: diff[l] += v and diff[r + 1] -= v
The change starts at l and stops just after r. The running sum later spreads it over l..r.
Why does the subarray-sum-equals-k pattern start with seen = {0: 1}?
Answer: So subarrays that start at index 0 are counted
A subarray from index 0 to r sums to k when the running total equals k, i.e. running − k = 0, the empty prefix.
For nums = [2, -2, 2] and k = 0, how many subarrays sum to k?Related
Answer: 2
[2, −2] and [−2, 2] both sum to 0; [2, −2, 2] sums to 2.
In Kadane's algorithm, what does cur hold at index i?Related
Answer: The best sum of a run that ends exactly at i
cur answers the smaller question "best run ending here"; best tracks the overall answer.
Why can the opposite-ends walk drop nums[left] when the sum is too small?Related
Answer: Because even with the largest value still in range, nums[right], the sum is too small
Every other partner for nums[left] is smaller than nums[right], so every pair with nums[left] is too small.
From Two pointers
Why must the numbers be positive for the shortest-subarray-with-sum-at-least-target window?Related
Answer: So removing an item from the left always lowers the sum, which makes "shrink while valid" correct
With negatives, removing an item can raise the sum, so the window can stop shrinking too early or miss the answer.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Arrays and strings” before its exam: Strings: building, comparing and counting, Maximum subarray: Kadane's algorithm, 2-D arrays (matrices).
- Module 2 exam: Arrays and strings · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Hashing, two pointers and sliding window. 61 topics left in the course before the final exam.