Prefix Sum
One-liner: precompute running totals once, so the sum of any range becomes a single subtraction.
The analogy
Think of a car's odometer. To know how far you drove between two gas stations you do not re-measure the road — you read the odometer at each stop and subtract. A prefix sum array is an odometer for your data: prefix[i] is the total up to position i, and any range sum is prefix[right+1] - prefix[left]. Pay O(n) once, then every query is O(1).
Recognition signals
Reach for prefix sums when you see:
- Many range queries (sum of a segment) on data that does not change between queries.
- A question about contiguous subarrays and their sum (or count, or product) — especially "how many subarrays sum to k".
- Negative numbers are allowed, so a sliding window cannot decide when to shrink.
- You catch yourself writing a nested loop whose inner loop just re-adds the same elements.
Step-by-step walkthrough
Take Subarray Sum Equals K on [1, 2, 3] with k = 3:
- Start with a map
{0: 1}— the empty prefix has total 0, seen once. - Add
1: running total 1. Look up1 - 3 = -2— nothing. Record total 1. - Add
2: running total 3. Look up3 - 3 = 0— found once, so subarray[1, 2]counts. Record total 3. - Add
3: running total 6. Look up6 - 3 = 3— found once, so subarray[3]counts.
Answer: 2. Each step asks one question: "how many earlier prefixes sit exactly k below me?"
Code template
Build the array with one extra leading zero. That zero removes every "what if the range starts at index 0" special case.
// Build returns prefix where prefix[i] = sum of nums[0:i] (length n+1).
// The sum of nums[l..r] (inclusive) is then prefix[r+1] - prefix[l].
func Build(nums []int) []int {
prefix := make([]int, len(nums)+1) // prefix[0] = 0: the empty prefix
for i, v := range nums {
prefix[i+1] = prefix[i] + v
}
return prefix
}Why each part exists:
prefix := make([]int, len(nums)+1)One extra slot so that prefix[0] = 0 represents the empty prefix. Without it, a range starting at index 0 needs a separate branch.
prefix[i+1] = prefix[i] + vEach total reuses the previous one — that reuse is the entire speedup. Never re-add from the start.
prefix[r+1] - prefix[l]Inclusive range l..r. The +1 offset comes from the leading zero; get this index shift wrong and every answer is off by one element.
Solutions
Range sum queries
The direct use: build once, answer many.
// NumArray: answer many range-sum queries in O(1) each.
type NumArray struct{ prefix []int }
func NewNumArray(nums []int) *NumArray { return &NumArray{prefix: Build(nums)} }
// SumRange returns the sum of nums[left..right] inclusive.
func (a *NumArray) SumRange(left, right int) int {
return a.prefix[right+1] - a.prefix[left]
}Subarray sum equals k
Here we do not even store the array. We keep a map from running total to how many times we have seen it, and ask for sum - k at each step. It works with negatives and zeros because we count every matching earlier prefix, not just one.
// SubarraySum: count subarrays whose sum equals k. Negatives allowed.
// Keep a map of "how many times have I seen each running total".
func SubarraySum(nums []int, k int) int {
seen := map[int]int{0: 1} // the empty prefix, so subarrays starting at 0 count
sum, count := 0, 0
for _, v := range nums {
sum += v
count += seen[sum-k] // every earlier prefix equal to sum-k ends a valid subarray here
seen[sum]++
}
return count
}Product of array except self
The same idea with multiplication: a prefix product from the left times a suffix product from the right. No division, so zeros are harmless.
// ProductExceptSelf: out[i] = product of every element except nums[i], no division.
// A prefix PRODUCT from the left, then a suffix product from the right.
func ProductExceptSelf(nums []int) []int {
out := make([]int, len(nums))
run := 1
for i := range nums {
out[i] = run // product of everything left of i
run *= nums[i]
}
run = 1
for i := len(nums) - 1; i >= 0; i-- {
out[i] *= run // multiply by everything right of i
run *= nums[i]
}
return out
}Equal zeros and ones
Relabel 0 as -1. Equal counts means a range summing to zero, i.e. two equal prefixes. For the longest range, store only the first index of each total.
// FindMaxLength: longest subarray with equal numbers of 0s and 1s.
// Treat 0 as -1; equal counts means a zero-sum range, so remember the FIRST index of each total.
func FindMaxLength(nums []int) int {
first := map[int]int{0: -1}
sum, best := 0, 0
for i, v := range nums {
if v == 0 {
sum--
} else {
sum++
}
if j, ok := first[sum]; ok {
best = max(best, i-j)
} else {
first[sum] = i
}
}
return best
}Two dimensions (optional)
Inclusion-exclusion: add the top and left prefixes, subtract the doubly counted corner.
// NumMatrix: 2D prefix sums, O(1) rectangle queries.
type NumMatrix struct{ p [][]int }
func NewNumMatrix(m [][]int) *NumMatrix {
rows := len(m)
cols := 0
if rows > 0 {
cols = len(m[0])
}
p := make([][]int, rows+1)
for i := range p {
p[i] = make([]int, cols+1)
}
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
p[r+1][c+1] = m[r][c] + p[r][c+1] + p[r+1][c] - p[r][c]
}
}
return &NumMatrix{p: p}
}
// SumRegion sums the rectangle from (r1,c1) to (r2,c2) inclusive.
func (n *NumMatrix) SumRegion(r1, c1, r2, c2 int) int {
return n.p[r2+1][c2+1] - n.p[r1][c2+1] - n.p[r2+1][c1] + n.p[r1][c1]
}Complexity
| Approach | Time | Space |
|---|---|---|
| Brute force (re-sum every range) | O(n) per query, O(n²) – O(n³) for all subarrays | O(1) |
| Prefix array | O(n) build, O(1) per query | O(n) |
| Prefix + hash map (count subarrays) | O(n) | O(n) |
Common mistakes
Practice ladder
Ordered Easy → Hard. None of the titles say "prefix". Ask: is a range total being recomputed?
- 1.Range Sum Query - ImmutableEasyMany queries, data never changes.
- 2.Find Pivot IndexEasyLeft total versus right total at each position.
- 3.Subarray Sum Equals KMediumNegatives are allowed — two earlier totals differ by k.
- 4.Product of Array Except SelfMediumDivision is not allowed. What do you know from each side?
- 5.Contiguous ArrayMediumRelabel one value so "equal" becomes "sums to zero".
- 6.Range Sum Query 2D - ImmutableMedium
- 7.Subarray Sums Divisible by KMediumTwo totals with the same remainder.
- 8.Number of Submatrices That Sum to TargetHardFix two rows, then reduce to a 1D problem you already know.
Which pattern? Drills
Unlabeled problems — pick the pattern, then read why.
Given an array of integers that can be negative, count how many contiguous subarrays add up to exactly k.
Which pattern?