Prefix Sum and Cumulative Arrays
1. Basic Prefix Sum Implementation
| Concept | Formula | Query Time | Use Case |
|---|---|---|---|
| Prefix Sum Array | prefix[i] = arr[0] + ... + arr[i] |
O(1) |
Range sum queries after O(n) preprocessing |
| Range Sum Query | sum[L,R] = prefix[R] - prefix[L-1] |
O(1) |
Get sum of elements from index L to R |
| Cumulative Sum | prefix[i] = prefix[i-1] + arr[i] |
O(n) build |
Progressive sum accumulation |
| Zero Initialization | prefix[0] = 0 or arr[0] |
N/A | Simplifies boundary handling |
Example: Range sum query using prefix sum
class NumArray {
constructor(nums) {
this.prefix = new Array(nums.length + 1).fill(0);
// Build prefix sum array
for (let i = 0; i < nums.length; i++) {
this.prefix[i + 1] = this.prefix[i] + nums[i];
}
}
sumRange(left, right) {
// Sum from left to right (inclusive)
return this.prefix[right + 1] - this.prefix[left];
}
}
// Example usage
const numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
console.log(numArray.sumRange(0, 2)); // Output: 1 (-2+0+3)
console.log(numArray.sumRange(2, 5)); // Output: -1 (3-5+2-1)
Example: Subarray sum equals k (prefix sum with HashMap)
function subarraySum(nums, k) {
const prefixSumCount = new Map();
prefixSumCount.set(0, 1); // Base case
let sum = 0;
let count = 0;
for (let num of nums) {
sum += num;
// Check if (sum - k) exists
if (prefixSumCount.has(sum - k)) {
count += prefixSumCount.get(sum - k);
}
// Add current sum to map
prefixSumCount.set(sum, (prefixSumCount.get(sum) || 0) + 1);
}
return count;
}
// Example: [1,1,1], k=2 -> Output: 2 ([1,1] appears twice)
2. 2D Prefix Sum for Matrix Range Queries
| Operation | Formula | Complexity | Purpose |
|---|---|---|---|
| 2D Prefix Build | P[i][j] = P[i-1][j] + P[i][j-1] - P[i-1][j-1] + M[i][j] |
O(m×n) |
Precompute cumulative sums for matrix |
| Rectangle Sum | Sum = P[r2][c2] - P[r1-1][c2] - P[r2][c1-1] + P[r1-1][c1-1] |
O(1) |
Query sum of submatrix in constant time |
| Inclusion-Exclusion | Add total, subtract overlaps, add back intersection | O(1) |
Avoid double counting in 2D space |
| Padding Approach | Add extra row/column of zeros at index 0 | Space: O(m×n) |
Simplify boundary conditions |
Example: 2D range sum query (immutable matrix)
class NumMatrix {
constructor(matrix) {
if (!matrix.length || !matrix[0].length) {
this.prefix = [];
return;
}
const m = matrix.length;
const n = matrix[0].length;
// Create prefix sum with padding
this.prefix = Array(m + 1).fill(0)
.map(() => Array(n + 1).fill(0));
// Build 2D prefix sum
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
this.prefix[i][j] = matrix[i-1][j-1]
+ this.prefix[i-1][j]
+ this.prefix[i][j-1]
- this.prefix[i-1][j-1];
}
}
}
sumRegion(row1, col1, row2, col2) {
// Sum of rectangle from (row1,col1) to (row2,col2)
return this.prefix[row2+1][col2+1]
- this.prefix[row1][col2+1]
- this.prefix[row2+1][col1]
+ this.prefix[row1][col1];
}
}
3. Difference Array for Range Updates
| Technique | Operation | Update Time | Application |
|---|---|---|---|
| Difference Array | diff[i] = arr[i] - arr[i-1] |
O(1) per update |
Efficient range increment/decrement |
| Range Update | diff[L] += val; diff[R+1] -= val |
O(1) |
Add value to range [L, R] efficiently |
| Reconstruct Array | arr[i] = arr[i-1] + diff[i] |
O(n) |
Build final array after all updates |
| Multiple Updates | Apply all updates, then reconstruct once | O(k + n) |
k updates + n reconstruction |
Example: Range addition using difference array
function getModifiedArray(length, updates) {
const diff = new Array(length + 1).fill(0);
// Apply all updates to difference array
for (let [start, end, inc] of updates) {
diff[start] += inc;
diff[end + 1] -= inc;
}
// Reconstruct array using prefix sum
const result = new Array(length);
result[0] = diff[0];
for (let i = 1; i < length; i++) {
result[i] = result[i-1] + diff[i];
}
return result;
}
// Example: length=5, updates=[[1,3,2],[2,4,3],[0,2,-2]]
// Output: [-2,0,3,5,3]
Example: Corporate flight bookings
function corpFlightBookings(bookings, n) {
const diff = new Array(n + 1).fill(0);
// Each booking adds seats to a range of flights
for (let [first, last, seats] of bookings) {
diff[first - 1] += seats; // 1-indexed
diff[last] -= seats;
}
// Build result array
const result = new Array(n);
result[0] = diff[0];
for (let i = 1; i < n; i++) {
result[i] = result[i-1] + diff[i];
}
return result;
}
4. Range Query Optimization Techniques
| Strategy | Preprocessing | Query | Best For |
|---|---|---|---|
| Prefix Sum | O(n) |
O(1) |
Immutable arrays, frequent sum queries |
| Sparse Table | O(n log n) |
O(1) |
Range min/max queries, immutable data |
| Segment Tree | O(n) |
O(log n) |
Mutable arrays, range queries + updates |
| Fenwick Tree (BIT) | O(n log n) |
O(log n) |
Prefix sums with updates, space efficient |
Example: Product of array except self (prefix/suffix product)
function productExceptSelf(nums) {
const n = nums.length;
const result = new Array(n);
// Build prefix products from left
result[0] = 1;
for (let i = 1; i < n; i++) {
result[i] = result[i-1] * nums[i-1];
}
// Build suffix products from right and combine
let suffixProduct = 1;
for (let i = n - 1; i >= 0; i--) {
result[i] *= suffixProduct;
suffixProduct *= nums[i];
}
return result;
}
// [1,2,3,4] -> [24,12,8,6] (O(n) time, O(1) extra space)
5. Prefix XOR and Bitwise Operations
| Operation | Property | Formula | Application |
|---|---|---|---|
| Prefix XOR | a ^ a = 0 |
xor[i] = xor[i-1] ^ arr[i] |
Range XOR queries, finding unique elements |
| Range XOR | Self-inverse property | xor[L,R] = xor[R] ^ xor[L-1] |
Cancel out common prefix |
| Single Number | XOR eliminates pairs | result = a[0] ^ ... ^ a[n] |
Find element appearing odd times |
| Prefix OR/AND | Bitwise cumulation | Similar to prefix sum pattern | Aggregate bitwise operations |
Example: XOR queries of a subarray
function xorQueries(arr, queries) {
const n = arr.length;
const prefixXor = new Array(n + 1).fill(0);
// Build prefix XOR array
for (let i = 0; i < n; i++) {
prefixXor[i + 1] = prefixXor[i] ^ arr[i];
}
// Answer queries
const result = [];
for (let [left, right] of queries) {
// XOR of range [left, right]
result.push(prefixXor[right + 1] ^ prefixXor[left]);
}
return result;
}
// arr=[1,3,4,8], queries=[[0,1],[1,2],[0,3],[3,3]]
// Output: [2,7,14,8]
Example: Count triplets with XOR equal to zero
function countTriplets(arr) {
const n = arr.length;
let count = 0;
// Build prefix XOR
const prefixXor = new Array(n + 1).fill(0);
for (let i = 0; i < n; i++) {
prefixXor[i + 1] = prefixXor[i] ^ arr[i];
}
// If prefixXor[i] === prefixXor[j],
// XOR from i to j-1 is 0
for (let i = 0; i < n; i++) {
for (let j = i + 1; j <= n; j++) {
if (prefixXor[i] === prefixXor[j]) {
// All subarrays in [i, j-1] work
count += j - i - 1;
}
}
}
return count;
}
6. Memory-efficient Prefix Sum Calculations
| Technique | Space Optimization | Trade-off | When to Use |
|---|---|---|---|
| Rolling Variables | O(n) → O(1) |
Can only query current position | One-pass calculations, streaming data |
| In-place Modification | Reuse input array | Destroys original data | When input can be modified |
| On-demand Computation | No preprocessing storage | Higher query time | Few queries, large data |
| Compressed Storage | Store only key indices | More complex logic | Sparse arrays, many zeros |
Example: Running sum with O(1) space
function runningSum(nums) {
// Modify array in-place
for (let i = 1; i < nums.length; i++) {
nums[i] += nums[i - 1];
}
return nums; // O(1) extra space
}
// Alternative: Using separate accumulator
function runningSumConstantSpace(nums) {
const result = new Array(nums.length);
let sum = 0;
for (let i = 0; i < nums.length; i++) {
sum += nums[i];
result[i] = sum;
}
return result;
}
Example: Max sum with rolling variables
function maxSubArray(nums) {
// Kadane's algorithm - O(1) space
let maxSoFar = nums[0];
let maxEndingHere = nums[0];
for (let i = 1; i < nums.length; i++) {
// Extend or restart subarray
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
// No array storage needed - just two variables
Note: Prefix sum transforms O(n) range queries into O(1) queries with O(n) preprocessing. The difference array technique enables O(1) range updates instead of O(n), crucial for multiple update operations.
Prefix Sum Pattern Summary
- 1D Prefix:
prefix[i] = prefix[i-1] + arr[i]for O(1) range sums - 2D Prefix: Inclusion-exclusion principle for rectangle queries
- Difference Array: Range updates in O(1), reconstruct in O(n)
- XOR Prefix: Use XOR properties:
a ^ a = 0,a ^ 0 = a - Space Optimization: Rolling variables for one-pass, in-place when allowed
Warning: When using 1-indexed arrays or padding with zeros, ensure consistent indexing in both construction and queries. For 2D prefix sums, be careful with the inclusion-exclusion formula - subtract both sides then add back the intersection to avoid double counting.