Prefix Sum Basics
Prefix Sum Basics
Introduction
Prefix Sum is a fundamental technique for efficiently calculating the sum of elements within various subarrays (ranges) of an array. It involves precomputing cumulative sums, allowing subsequent range sum queries to be answered in constant time. This pattern is crucial for optimizing solutions to many array-based problems.
Quick Overview
Precomputing cumulative sums to answer range sum queries in O(1) time after an O(N) setup.
Motivation
When dealing with an array, calculating the sum of elements within a specific range [i, j] naively requires iterating from i to j, taking O(j-i+1) time. If multiple such range sum queries are performed, the total time complexity quickly becomes O(Q*N) for Q queries on an array of size N. This inefficiency makes brute-force approaches impractical for large datasets or stringent time limits, necessitating a more optimized solution.
Real Life Analogy
Imagine you have a daily sales report for a month, showing sales figures for each day. If your manager frequently asks for the total sales between day 5 and day 10, or day 12 and day 20, you wouldn't want to add up each day's sales every single time. Instead, you'd create a running total: a 'cumulative sales' report. This report lists the total sales from day 1 up to each specific day. To find sales between day 5 and day 10, you simply take the cumulative sales up to day 10 and subtract the cumulative sales up to day 4. This is much faster than summing individual days.
Core Idea
The core idea of Prefix Sum is to create an auxiliary array, P, where P[i] stores the sum of the first i elements of the original array A. That is, P[i] = A[0] + A[1] + ... + A[i-1]. With this precomputed array, the sum of any subarray A[left...right] (inclusive) can be calculated in O(1) time as P[right + 1] - P[left]. A common convention is to set P[0] = 0 to simplify calculations for ranges starting at index 0.
Key Insight
The 'aha!' moment is realizing that any subarray sum sum(A[left...right]) can be expressed as a difference of two precomputed prefix sums: PrefixSum[right+1] - PrefixSum[left]. This transforms an O(N) query into an O(1) lookup after an initial O(N) precomputation.
Prerequisites
- Basic array manipulation and indexing (0-indexed vs 1-indexed).
- Understanding of loops and iteration.
- Familiarity with time and space complexity analysis (Big O notation).
Visualization
Original Array A: [ 1, 2, 3, 4, 5 ] (N=5)
Indices: 0 1 2 3 4
Prefix Sum Array P (size N+1):
P[0] = 0
P[1] = A[0] = 1
P[2] = A[0]+A[1] = 1+2 = 3
P[3] = A[0]+A[1]+A[2] = 1+2+3 = 6
P[4] = A[0]+A[1]+A[2]+A[3] = 1+2+3+4 = 10
P[5] = A[0]+A[1]+A[2]+A[3]+A[4] = 1+2+3+4+5 = 15
P: [ 0, 1, 3, 6, 10, 15 ]
Indices: 0 1 2 3 4 5
To find sum(A[1...3]): A[1] + A[2] + A[3] = 2 + 3 + 4 = 9
Using P: P[3+1] - P[1] = P[4] - P[1] = 10 - 1 = 9
Dry Run
Let's trace the creation and use of a prefix sum array for A = [5, 1, 8, 2].
1. Initialization:
Create a prefixSum array of size N+1 (where N is the length of A, which is 4). Initialize prefixSum[0] to 0.
prefixSum = [0, _, _, _, _]
2. Building the Prefix Sum Array:
i = 0:prefixSum[1] = prefixSum[0] + A[0] = 0 + 5 = 5prefixSum = [0, 5, _, _, _]i = 1:prefixSum[2] = prefixSum[1] + A[1] = 5 + 1 = 6prefixSum = [0, 5, 6, _, _]i = 2:prefixSum[3] = prefixSum[2] + A[2] = 6 + 8 = 14prefixSum = [0, 5, 6, 14, _]i = 3:prefixSum[4] = prefixSum[3] + A[3] = 14 + 2 = 16prefixSum = [0, 5, 6, 14, 16]
3. Querying a Range Sum (e.g., sum of A[1...3] inclusive):
- We want
A[1] + A[2] + A[3] = 1 + 8 + 2 = 11. - Using prefix sums:
sum(A[left...right]) = prefixSum[right + 1] - prefixSum[left] - Here,
left = 1,right = 3. prefixSum[3 + 1] - prefixSum[1] = prefixSum[4] - prefixSum[1]16 - 5 = 11. The result matches the direct sum, demonstrating the efficiency.
Algorithm
- Initialize a
prefixSumarray of sizeN + 1, whereNis the length of the original arrayA. - Set
prefixSum[0] = 0. - Iterate from
i = 0toN - 1(inclusive) for the original arrayA. - For each
i, calculateprefixSum[i + 1] = prefixSum[i] + A[i]. - To query the sum of
A[left...right](inclusive, 0-indexed): returnprefixSum[right + 1] - prefixSum[left].
Pseudocode
function buildPrefixSumArray(arr):
n = length(arr)
prefixSum = array of size n + 1
prefixSum[0] = 0 // Initialize with 0 to handle sums starting from index 0
for i from 0 to n-1:
prefixSum[i+1] = prefixSum[i] + arr[i]
return prefixSum
function queryRangeSum(prefixSum, left, right):
// 'left' and 'right' are 0-indexed bounds for the original array
// sum(arr[left...right]) = (arr[0] + ... + arr[right]) - (arr[0] + ... + arr[left-1])
// This maps to prefixSum[right+1] - prefixSum[left]
// Ensure valid indices, though usually handled by problem constraints
if left < 0 or right >= length(prefixSum) - 1 or left > right:
return ERROR_OR_HANDLE_INVALID_RANGE
return prefixSum[right + 1] - prefixSum[left]
Time Complexity
O(N) for construction, O(1) for each query - Building the prefix sum array requires a single pass through the input array, taking O(N) time. Once constructed, any range sum query involves two array lookups and one subtraction, which are constant time operations, O(1).
Space Complexity
O(N) - An additional array of size N+1 is created to store the prefix sums, where N is the size of the original array. This auxiliary space is directly proportional to the input size.
Advantages
- Efficient Querying: Provides
O(1)time complexity for each range sum query after initial setup. - Simplicity: The core logic is straightforward to understand and implement.
- Versatility: Forms the basis for more advanced techniques (e.g., 2D prefix sums, difference arrays, hash map optimizations for specific sum problems).
Limitations
- Requires Additional Space: The
O(N)space complexity might be a concern for extremely large arrays if memory is strictly limited. - Static Array: If the original array is frequently updated (elements change), the prefix sum array needs to be recomputed or dynamically maintained, which can negate the
O(1)query advantage. For dynamic updates, Fenwick Trees (BITs) or Segment Trees are more appropriate. - Limited to Associative Operations: Primarily works for operations like sum, product, XOR, where
op(a, b, c) = op(op(a, b), c). Does not directly apply to non-associative operations like median or mode.
Common Mistakes
- Off-by-one errors: Incorrectly using
prefixSum[i]instead ofprefixSum[i+1]orprefixSum[left-1]instead ofprefixSum[left], especially when handling 0-indexed vs 1-indexed original arrays. - Not initializing
prefixSum[0] = 0: This leads to incorrect sums for ranges starting at index 0. - Incorrectly handling
left = 0: IfprefixSum[left-1]is used without theprefixSum[0]=0convention, an out-of-bounds error or incorrect sum will occur. - Forgetting about integer overflow: When summing a large number of elements or elements with large values, the sum can exceed the maximum value for standard integer types (e.g.,
intin C++/Java), requiringlong longorlong.
Edge Cases
- Empty array: The prefix sum array will just be
[0](ornull/empty depending on implementation). - Single-element array:
A = [val].prefixSum = [0, val]. QueryA[0...0]would beprefixSum[1] - prefixSum[0] = val - 0 = val. - Query range
left == right:sum(A[i...i])should yieldA[i]. Using formula:prefixSum[i+1] - prefixSum[i] = A[i], which is correct. - Query range starts at index 0:
sum(A[0...right]) = prefixSum[right+1] - prefixSum[0] = prefixSum[right+1]. - Query range spans the entire array:
sum(A[0...N-1]) = prefixSum[N] - prefixSum[0] = prefixSum[N].
Recognition Tricks
- Problems asking for the sum of elements in subarrays or ranges.
- Problems involving multiple queries for subarray sums.
- Finding a subarray with a specific sum or property related to its sum (often combined with a hash map).
- Questions where a fixed-size window (sliding window) needs its sum updated efficiently.
- When a property for a segment
[i, j]can be derived from the property of[0, j]and[0, i-1](e.g.,P(i, j) = P(0, j) - P(0, i-1)).
When To Use
- When you need to perform many range sum queries on an array that does not change frequently.
- As a precursor to more complex array problems involving subarray sums or properties related to cumulative values (e.g., finding the longest subarray with sum
K). - In 2D scenarios, for calculating sums within submatrices (2D Prefix Sums).
- When constant-time range queries are critical for overall solution efficiency.
Interview Perspective
Prefix Sum is a foundational pattern frequently tested in interviews to assess a candidate's understanding of basic optimization techniques. Interviewers look for: 1) recognition of the pattern when faced with range query problems; 2) correct implementation, especially regarding off-by-one errors and edge cases (empty array, single element, full array range); 3) ability to articulate the time and space complexity; and 4) creativity in extending the basic concept (e.g., using prefix sums with hash maps for 'subarray sum equals K' type problems, or applying it in a 2D context). It's a quick way to demonstrate analytical thinking and problem-solving fundamentals.
Key Takeaways
- Prefix Sum transforms
O(N)range sum queries intoO(1)queries after anO(N)precomputation. - It uses
O(N)auxiliary space to store cumulative sums. - The core formula for sum
A[left...right]isP[right+1] - P[left](withP[0]=0). - Beware of off-by-one errors and handle edge cases like
left=0correctly. - A key building block for many medium-to-hard array problems.
Practice Problems
(See Practice Problems index below)
Implementations
Practice Problems
| LeetCode Problem | ID | Difficulty | Pattern | Official Link | Leet Vision Solution |
|---|---|---|---|---|---|
| Range Sum Query - Immutable | 303 | Easy | Prefix Sum | Solve on LeetCode ↗ | Video Solution ↗ |
| Subarray Sum Equals K | 560 | Medium | Prefix Sum + Hash Map | Solve on LeetCode ↗ | Video Solution ↗ |
| Find Pivot Index | 724 | Easy | Prefix Sum | Solve on LeetCode ↗ | Video Solution ↗ |
| Minimum Size Subarray Sum | 209 | Medium | Prefix Sum / Sliding Window | Solve on LeetCode ↗ | Video Solution ↗ |