Prefix Sums
Precompute running totals once so any range sum is a single subtraction, and count subarrays with a target sum in one pass.
How It Works
A prefix sum array stores running totals: prefix[i] is the sum of the first i values, nums[0] through nums[i - 1]. Building it takes one pass. After that, the sum of any range is a single subtraction: nums[l..r] adds up to prefix[r + 1] - prefix[l]. The total through r includes everything before l as well, and subtracting prefix[l] takes that part back out.
i 0 1 2 3 4 5 6
nums[i] -2 0 3 -5 2 -1
prefix[i] 0 -2 -2 1 -4 -2 -3
sum of nums[2..5] = prefix[6] - prefix[2]
= -3 - (-2)
= -1prefix is one longer than nums. The leading 0 means a range that starts at index 0 needs no special case.Prefix Sums, Step by Step
Step 1 of 10
nums
prefix
- Being written
- Read for this step
- Built, or part of the answer
- Subtracted back out
prefix is one longer than nums. prefix[0] = 0 is the sum of no values at all, so a range that starts at index 0 needs no special case.
All 10 steps as text
prefixis one longer thannums.prefix[0] = 0is the sum of no values at all, so a range that starts at index0needs no special case.prefix[1] = 0 + (-2) = -2. Each total is the one before it plus one more value, so the whole array takes a single pass.prefix[2] = -2 + 0 = -2, the sum ofnums[0..1]. One addition per step, with no rescanning.prefix[3] = -2 + 3 = 1, the sum ofnums[0..2]. One addition per step, with no rescanning.prefix[4] = 1 + (-5) = -4, the sum ofnums[0..3]. One addition per step, with no rescanning.prefix[5] = -4 + 2 = -2, the sum ofnums[0..4]. One addition per step, with no rescanning.prefix[6] = -2 + (-1) = -3. That's every value, built in O(n). Now any range sum is one subtraction.- Sum
nums[2..5].prefix[6] = -3covers everything up to index5, which includesnums[0..1]as well. - Take back
prefix[2] = -2, the part beforeleft:-3 - (-2) = -1. That's the sum of[3, -5, 2, -1]in O(1), with no loop. - A range from index
0works the same way:prefix[3] - prefix[0] = 1 - 0 = 1. The leading0is what saves the special case.
The idea shows up in three common shapes:
- Range queries. Build once in O(n), then answer each "sum from
ltor" in O(1). The 2D version does the same for rectangles in a grid. - Prefix sums plus a hash map. A subarray sums to
kexactly when two prefix sums differ byk. Walk the array, keep a count of the prefix sums you've seen, and you can count those subarrays in one pass, negative numbers included. - Other running totals. Anything you can accumulate left to right works the same way: products, XORs, counts of some property. Product of Array Except Self below combines a running product from the left with one from the right.
When to Reach for It
- Many queries ask for the sum of a range in an array that doesn't change between them.
- You need to count or find subarrays whose sum equals
k, or is divisible byk. - The array can hold negative numbers, so a sliding window can't tell when to shrink.
- Each position's answer combines everything to its left with everything to its right.
Complexity
| Operation | Time | Space |
|---|---|---|
| Build the prefix array | O(n) | O(n) |
| Range sum query | O(1) | O(1) |
| Count subarrays that sum to k (prefix sums + hash map) | O(n) | O(n) |
The Template
Range Sum Query
buildPrefix makes an array one longer than nums. It starts at 0 and adds one value per step, so prefix[i + 1] = prefix[i] + nums[i]. rangeSum then answers any inclusive range [left, right] with prefix[right + 1] - prefix[left]. Build once, then query as often as you like.
Watch the index shift. prefix[i] covers the values before index i, so the right end of a range needs the + 1 and the left end doesn't.
Time O(n) to build, O(1) per querySpace O(n)
def build_prefix(nums: list[int]) -> list[int]:
"""prefix[i] is the sum of the first i values of `nums`, so prefix[0] is 0
and the list is one longer than `nums`."""
prefix = [0] * (len(nums) + 1)
for i, num in enumerate(nums):
prefix[i + 1] = prefix[i] + num
return prefix
def range_sum(prefix: list[int], left: int, right: int) -> int:
"""Sum of nums[left..right], both ends included, in O(1)."""
# Everything up to right, minus everything before left.
return prefix[right + 1] - prefix[left]
if __name__ == "__main__":
prefix = build_prefix([-2, 0, 3, -5, 2, -1])
print(prefix) # [0, -2, -2, 1, -4, -2, -3]
print(range_sum(prefix, 0, 2)) # 1
print(range_sum(prefix, 2, 5)) # -1
print(range_sum(prefix, 3, 3)) # -5// prefix[i] is the sum of the first i values of `nums`, so prefix[0] is 0
// and the array is one longer than `nums`.
export function buildPrefix(nums: number[]): number[] {
const prefix = new Array<number>(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++) prefix[i + 1] = prefix[i] + nums[i];
return prefix;
}
// Sum of nums[left..right], both ends included, in O(1).
export function rangeSum(prefix: number[], left: number, right: number): number {
// Everything up to right, minus everything before left.
return prefix[right + 1] - prefix[left];
}
const prefix = buildPrefix([-2, 0, 3, -5, 2, -1]);
console.log(JSON.stringify(prefix)); // [0,-2,-2,1,-4,-2,-3]
console.log(rangeSum(prefix, 0, 2)); // 1
console.log(rangeSum(prefix, 2, 5)); // -1
console.log(rangeSum(prefix, 3, 3)); // -5package main
import "fmt"
// buildPrefix returns prefix, where prefix[i] is the sum of the first i values
// of nums. prefix[0] is 0 and the slice is one longer than nums.
func buildPrefix(nums []int) []int {
prefix := make([]int, len(nums)+1)
for i, num := range nums {
prefix[i+1] = prefix[i] + num
}
return prefix
}
// rangeSum returns the sum of nums[left..right], both ends included, in O(1).
func rangeSum(prefix []int, left, right int) int {
// Everything up to right, minus everything before left.
return prefix[right+1] - prefix[left]
}
func main() {
prefix := buildPrefix([]int{-2, 0, 3, -5, 2, -1})
fmt.Println(prefix) // [0 -2 -2 1 -4 -2 -3]
fmt.Println(rangeSum(prefix, 0, 2)) // 1
fmt.Println(rangeSum(prefix, 2, 5)) // -1
fmt.Println(rangeSum(prefix, 3, 3)) // -5
}using System;
public class Solution
{
// prefix[i] is the sum of the first i values of nums, so prefix[0] is 0
// and the array is one longer than nums.
public static int[] BuildPrefix(int[] nums)
{
var prefix = new int[nums.Length + 1];
for (int i = 0; i < nums.Length; i++) prefix[i + 1] = prefix[i] + nums[i];
return prefix;
}
// Sum of nums[left..right], both ends included, in O(1).
public static int RangeSum(int[] prefix, int left, int right)
{
// Everything up to right, minus everything before left.
return prefix[right + 1] - prefix[left];
}
public static void Main()
{
int[] prefix = BuildPrefix(new[] { -2, 0, 3, -5, 2, -1 });
Console.WriteLine("[" + string.Join(", ", prefix) + "]"); // [0, -2, -2, 1, -4, -2, -3]
Console.WriteLine(RangeSum(prefix, 0, 2)); // 1
Console.WriteLine(RangeSum(prefix, 2, 5)); // -1
Console.WriteLine(RangeSum(prefix, 3, 3)); // -5
}
}import java.util.Arrays;
public class Solution {
// prefix[i] is the sum of the first i values of nums, so prefix[0] is 0
// and the array is one longer than nums.
static int[] buildPrefix(int[] nums) {
int[] prefix = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) prefix[i + 1] = prefix[i] + nums[i];
return prefix;
}
// Sum of nums[left..right], both ends included, in O(1).
static int rangeSum(int[] prefix, int left, int right) {
// Everything up to right, minus everything before left.
return prefix[right + 1] - prefix[left];
}
public static void main(String[] args) {
int[] prefix = buildPrefix(new int[] {-2, 0, 3, -5, 2, -1});
System.out.println(Arrays.toString(prefix)); // [0, -2, -2, 1, -4, -2, -3]
System.out.println(rangeSum(prefix, 0, 2)); // 1
System.out.println(rangeSum(prefix, 2, 5)); // -1
System.out.println(rangeSum(prefix, 3, 3)); // -5
}
}Worked Problems
Each solution stays folded until you open it, so you can try the problem first. Every one is a complete program in Python, TypeScript, Go, C# and Java, run and checked against its expected output.
Problem
Given an array of integers nums (negative values allowed) and an integer k, count the contiguous, non-empty subarrays whose values add up to exactly k.
Example: nums = [3, 4, 7, 2, -3, 1, 4, 2] and k = 7 returns 4, for [3, 4], [7], [7, 2, -3, 1] and [1, 4, 2].
Show SolutionHide Solution
Approach
A subarray nums[i..j] sums to k exactly when prefix[j + 1] - prefix[i] = k, which rearranges to prefix[i] = prefix[j + 1] - k. So walk the array keeping a running sum, and at each step ask how many earlier prefixes equal running - k. A hash map from each prefix sum to how often you've seen it answers that in O(1).
- Seed the map with
{0: 1}. That's the empty prefix before index 0. Without it you miss every subarray that starts at the beginning. - Look up
running - kbefore addingrunningto the map. Otherwise, whenk = 0, the current prefix matches itself and counts an empty subarray. - Store counts, not just whether a sum was seen. The same prefix sum comes back whenever a stretch adds up to 0, and each earlier copy starts a different subarray.
Counting Subarrays That Sum to k
Step 1 of 10
nums
seen
- Current value and its prefix
- Earlier prefix it matches
- Subarray that sums to k
Seed seen with {0: 1}, the empty prefix before index 0. A subarray sums to 7 when the running total minus an earlier prefix is 7.
All 10 steps as text
- Seed
seenwith{0: 1}, the empty prefix before index0. A subarray sums to7when the running total minus an earlier prefix is7. running = 3, so look for3 - 7 = -4.seenhas no-4, so no subarray ending here sums to7.running = 7, so look for7 - 7 = 0. The seeded empty prefix matches, so[3, 4]sums to7andcount = 1.running = 14, so look for14 - 7 = 7.seenhas it, so[7]sums to7andcount = 2.running = 16, so look for16 - 7 = 9.seenhas no9, so no subarray ending here sums to7.running = 13, so look for13 - 7 = 6. The total just went down, which would break a sliding window. Prefix sums don't mind.running = 14, so look for14 - 7 = 7.seenhas it, so[7, 2, -3, 1]sums to7andcount = 3.14itself comes back, so its count goes to2.running = 18, so look for18 - 7 = 11.seenhas no11, so no subarray ending here sums to7.running = 20, so look for20 - 7 = 13.seenhas it, so[1, 4, 2]sums to7andcount = 4.- One pass and
count = 4. Each value costs one lookup and one store, both O(1) on average, so the whole count is O(n) even with negative numbers.
A sliding window can't solve this one. With negative numbers, growing the window can lower its sum, so there's no rule for when to move left. The prefix sum view doesn't care about signs.
Time O(n)Space O(n)
def subarray_sum(nums: list[int], k: int) -> int:
"""Number of contiguous subarrays of `nums` whose values add up to `k`."""
# How many times each prefix sum has appeared so far. The empty prefix
# (sum 0) counts once, so subarrays that start at index 0 are found too.
seen = {0: 1}
running = 0
count = 0
for num in nums:
running += num
# Every earlier prefix equal to running - k starts a subarray summing to k.
# Look it up before recording running, or k = 0 would match itself.
count += seen.get(running - k, 0)
seen[running] = seen.get(running, 0) + 1
return count
if __name__ == "__main__":
print(subarray_sum([1, 1, 1], 2)) # 2
print(subarray_sum([1, 2, 3], 3)) # 2
print(subarray_sum([3, 4, 7, 2, -3, 1, 4, 2], 7)) # 4
print(subarray_sum([1, -1, 0], 0)) # 3// Number of contiguous subarrays of `nums` whose values add up to `k`.
export function subarraySum(nums: number[], k: number): number {
// How many times each prefix sum has appeared so far. The empty prefix
// (sum 0) counts once, so subarrays that start at index 0 are found too.
const seen = new Map<number, number>([[0, 1]]);
let running = 0;
let count = 0;
for (const num of nums) {
running += num;
// Every earlier prefix equal to running - k starts a subarray summing to k.
// Look it up before recording running, or k = 0 would match itself.
count += seen.get(running - k) ?? 0;
seen.set(running, (seen.get(running) ?? 0) + 1);
}
return count;
}
console.log(subarraySum([1, 1, 1], 2)); // 2
console.log(subarraySum([1, 2, 3], 3)); // 2
console.log(subarraySum([3, 4, 7, 2, -3, 1, 4, 2], 7)); // 4
console.log(subarraySum([1, -1, 0], 0)); // 3package main
import "fmt"
// subarraySum returns the number of contiguous subarrays of nums whose values
// add up to k.
func subarraySum(nums []int, k int) int {
// How many times each prefix sum has appeared so far. The empty prefix
// (sum 0) counts once, so subarrays that start at index 0 are found too.
seen := map[int]int{0: 1}
running := 0
count := 0
for _, num := range nums {
running += num
// Every earlier prefix equal to running - k starts a subarray summing to k.
// Look it up before recording running, or k = 0 would match itself.
count += seen[running-k]
seen[running]++
}
return count
}
func main() {
fmt.Println(subarraySum([]int{1, 1, 1}, 2)) // 2
fmt.Println(subarraySum([]int{1, 2, 3}, 3)) // 2
fmt.Println(subarraySum([]int{3, 4, 7, 2, -3, 1, 4, 2}, 7)) // 4
fmt.Println(subarraySum([]int{1, -1, 0}, 0)) // 3
}using System;
using System.Collections.Generic;
public class Solution
{
// Number of contiguous subarrays of nums whose values add up to k.
public static int SubarraySum(int[] nums, int k)
{
// How many times each prefix sum has appeared so far. The empty prefix
// (sum 0) counts once, so subarrays that start at index 0 are found too.
var seen = new Dictionary<int, int> { [0] = 1 };
int running = 0, count = 0;
foreach (int num in nums)
{
running += num;
// Every earlier prefix equal to running - k starts a subarray summing to k.
// Look it up before recording running, or k = 0 would match itself.
count += seen.GetValueOrDefault(running - k);
seen[running] = seen.GetValueOrDefault(running) + 1;
}
return count;
}
public static void Main()
{
Console.WriteLine(SubarraySum(new[] { 1, 1, 1 }, 2)); // 2
Console.WriteLine(SubarraySum(new[] { 1, 2, 3 }, 3)); // 2
Console.WriteLine(SubarraySum(new[] { 3, 4, 7, 2, -3, 1, 4, 2 }, 7)); // 4
Console.WriteLine(SubarraySum(new[] { 1, -1, 0 }, 0)); // 3
}
}import java.util.HashMap;
import java.util.Map;
public class Solution {
// Number of contiguous subarrays of nums whose values add up to k.
static int subarraySum(int[] nums, int k) {
// How many times each prefix sum has appeared so far. The empty prefix
// (sum 0) counts once, so subarrays that start at index 0 are found too.
Map<Integer, Integer> seen = new HashMap<>();
seen.put(0, 1);
int running = 0, count = 0;
for (int num : nums) {
running += num;
// Every earlier prefix equal to running - k starts a subarray summing to k.
// Look it up before recording running, or k = 0 would match itself.
count += seen.getOrDefault(running - k, 0);
seen.merge(running, 1, Integer::sum);
}
return count;
}
public static void main(String[] args) {
System.out.println(subarraySum(new int[] {1, 1, 1}, 2)); // 2
System.out.println(subarraySum(new int[] {1, 2, 3}, 3)); // 2
System.out.println(subarraySum(new int[] {3, 4, 7, 2, -3, 1, 4, 2}, 7)); // 4
System.out.println(subarraySum(new int[] {1, -1, 0}, 0)); // 3
}
}Problem
Given an array nums, return an array whose value at position i is the product of every element except nums[i]. It has to run in O(n) time without using division.
Example: [1, 2, 3, 4] returns [24, 12, 8, 6]. Position 0 is 2 * 3 * 4 = 24.
Show SolutionHide Solution
Approach
The product of everything except nums[i] is the product of everything to its left times the product of everything to its right. Those are a prefix product and a suffix product, and each takes one pass to build.
nums 1 2 3 4 left 1 1 2 6 product of everything before i right 24 12 4 1 product of everything after i answer 24 12 8 6 left * right
You don't need two extra arrays. A forward pass fills the answer with the left products. A backward pass keeps the right product in a single variable, suffix, and multiplies it in. The output array is the only memory that grows with n.
Time O(n)Space O(1) beyond the output
def product_except_self(nums: list[int]) -> list[int]:
"""answer[i] is the product of every value in `nums` except nums[i], with no division."""
answer = [1] * len(nums)
# Forward pass: answer[i] gets the product of everything left of i.
prefix = 1
for i in range(len(nums)):
answer[i] = prefix
prefix *= nums[i]
# Backward pass: multiply in the product of everything right of i.
suffix = 1
for i in range(len(nums) - 1, -1, -1):
answer[i] *= suffix
suffix *= nums[i]
return answer
if __name__ == "__main__":
print(product_except_self([1, 2, 3, 4])) # [24, 12, 8, 6]
print(product_except_self([-1, 1, 0, -3, 3])) # [0, 0, 9, 0, 0]
print(product_except_self([5, 2])) # [2, 5]// answer[i] is the product of every value in `nums` except nums[i], with no division.
export function productExceptSelf(nums: number[]): number[] {
const answer = new Array<number>(nums.length);
// Forward pass: answer[i] gets the product of everything left of i.
let prefix = 1;
for (let i = 0; i < nums.length; i++) {
answer[i] = prefix;
prefix *= nums[i];
}
// Backward pass: multiply in the product of everything right of i.
let suffix = 1;
for (let i = nums.length - 1; i >= 0; i--) {
answer[i] *= suffix;
suffix *= nums[i];
}
return answer;
}
console.log(JSON.stringify(productExceptSelf([1, 2, 3, 4]))); // [24,12,8,6]
console.log(JSON.stringify(productExceptSelf([-1, 1, 0, -3, 3]))); // [0,0,9,0,0]
console.log(JSON.stringify(productExceptSelf([5, 2]))); // [2,5]package main
import "fmt"
// productExceptSelf returns answer, where answer[i] is the product of every
// value in nums except nums[i], with no division.
func productExceptSelf(nums []int) []int {
answer := make([]int, len(nums))
// Forward pass: answer[i] gets the product of everything left of i.
prefix := 1
for i := 0; i < len(nums); i++ {
answer[i] = prefix
prefix *= nums[i]
}
// Backward pass: multiply in the product of everything right of i.
suffix := 1
for i := len(nums) - 1; i >= 0; i-- {
answer[i] *= suffix
suffix *= nums[i]
}
return answer
}
func main() {
fmt.Println(productExceptSelf([]int{1, 2, 3, 4})) // [24 12 8 6]
fmt.Println(productExceptSelf([]int{-1, 1, 0, -3, 3})) // [0 0 9 0 0]
fmt.Println(productExceptSelf([]int{5, 2})) // [2 5]
}using System;
public class Solution
{
// answer[i] is the product of every value in nums except nums[i], with no division.
public static int[] ProductExceptSelf(int[] nums)
{
var answer = new int[nums.Length];
// Forward pass: answer[i] gets the product of everything left of i.
int prefix = 1;
for (int i = 0; i < nums.Length; i++)
{
answer[i] = prefix;
prefix *= nums[i];
}
// Backward pass: multiply in the product of everything right of i.
int suffix = 1;
for (int i = nums.Length - 1; i >= 0; i--)
{
answer[i] *= suffix;
suffix *= nums[i];
}
return answer;
}
public static void Main()
{
Console.WriteLine(Format(ProductExceptSelf(new[] { 1, 2, 3, 4 }))); // [24, 12, 8, 6]
Console.WriteLine(Format(ProductExceptSelf(new[] { -1, 1, 0, -3, 3 }))); // [0, 0, 9, 0, 0]
Console.WriteLine(Format(ProductExceptSelf(new[] { 5, 2 }))); // [2, 5]
}
static string Format(int[] values) => "[" + string.Join(", ", values) + "]";
}import java.util.Arrays;
public class Solution {
// answer[i] is the product of every value in nums except nums[i], with no division.
static int[] productExceptSelf(int[] nums) {
int[] answer = new int[nums.length];
// Forward pass: answer[i] gets the product of everything left of i.
int prefix = 1;
for (int i = 0; i < nums.length; i++) {
answer[i] = prefix;
prefix *= nums[i];
}
// Backward pass: multiply in the product of everything right of i.
int suffix = 1;
for (int i = nums.length - 1; i >= 0; i--) {
answer[i] *= suffix;
suffix *= nums[i];
}
return answer;
}
public static void main(String[] args) {
System.out.println(Arrays.toString(productExceptSelf(new int[] {1, 2, 3, 4}))); // [24, 12, 8, 6]
System.out.println(Arrays.toString(productExceptSelf(new int[] {-1, 1, 0, -3, 3}))); // [0, 0, 9, 0, 0]
System.out.println(Arrays.toString(productExceptSelf(new int[] {5, 2}))); // [2, 5]
}
}Common Pitfalls
- Mixing up the indexes: the sum of
nums[l..r]isprefix[r + 1] - prefix[l], notprefix[r] - prefix[l]. - Forgetting to seed the hash map with
{0: 1}, which misses every subarray that starts at index 0. - Adding the current prefix to the map before looking up
running - k. Withk = 0that counts an empty subarray. - Overflowing a 32-bit integer. Running totals grow with the array, so use a 64-bit type when the values or the array are large.
- Keeping a prefix array for data that changes. Every update makes all the later prefixes stale.
Practice
More problems to try on your own.