# Prefix Sum Pattern ## Overview Prefix sum is a powerful technique for efficiently answering range sum queries and solving various array problems. It involves precomputing cumulative sums to allow O(1) range queries. ## Core Concepts ### Prefix Sum Definition The prefix sum array `prefix` is defined such that: ``` prefix[i] = arr[0] + arr[1] + ... + arr[i] ``` Given a range query from index `i` to `j`: ``` sum(i, j) = prefix[j] - prefix[i-1] ``` ### Basic Implementation ```typescript function buildPrefixSum(arr: number[]): number[] { const prefix = new Array(arr.length); prefix[0] = arr[0]; for (let i = 1; i < arr.length; i++) { prefix[i] = prefix[i-1] + arr[i]; } return prefix; } function rangeSum(prefix: number[], left: number, right: number): number { if (left === 0) { return prefix[right]; } return prefix[right] - prefix[left-1]; } ``` ## Variations ### 1. Prefix Product ```typescript function buildPrefixProduct(arr: number[]): number[] { const prefix = new Array(arr.length); prefix[0] = arr[0]; for (let i = 1; i < arr.length; i++) { prefix[i] = prefix[i-1] * arr[i]; } return prefix; } function rangeProduct(prefix: number[], left: number, right: number): number { if (left === 0) { return prefix[right]; } return prefix[right] / prefix[left-1]; } ``` ### 2. 2D Prefix Sum ```typescript function build2DPrefixSum(matrix: number[][]): number[][] { const rows = matrix.length; const cols = matrix[0].length; const prefix = Array.from({ length: rows + 1 }, () => Array(cols + 1).fill(0)); for (let i = 1; i <= rows; i++) { for (let j = 1; j <= cols; j++) { prefix[i][j] = matrix[i-1][j-1] + prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1]; } } return prefix; } function range2DSum(prefix: number[][], row1: number, col1: number, row2: number, col2: number): number { return prefix[row2+1][col2+1] - prefix[row1][col2+1] - prefix[row2+1][col1] + prefix[row1][col1]; } ``` ### 3. Prefix XOR ```typescript function buildPrefixXOR(arr: number[]): number[] { const prefix = new Array(arr.length); prefix[0] = arr[0]; for (let i = 1; i < arr.length; i++) { prefix[i] = prefix[i-1] ^ arr[i]; } return prefix; } function rangeXOR(prefix: number[], left: number, right: number): number { if (left === 0) { return prefix[right]; } return prefix[right] ^ prefix[left-1]; } ``` ## Applications ### 1. Subarray Sum Equals K ```typescript function subarraySum(nums: number[], k: number): number { const prefixSum = { 0: 1 }; let currentSum = 0; let count = 0; for (const num of nums) { currentSum += num; // Check if there's a prefix that sums to currentSum - k if (prefixSum[currentSum - k]) { count += prefixSum[currentSum - k]; } // Store current prefix sum prefixSum[currentSum] = (prefixSum[currentSum] || 0) + 1; } return count; } ``` ### 2. Find Maximum Subarray Sum ```typescript function maxSubArray(nums: number[]): number { let maxSum = nums[0]; let currentSum = nums[0]; for (let i = 1; i < nums.length; i++) { currentSum = Math.max(nums[i], currentSum + nums[i]); maxSum = Math.max(maxSum, currentSum); } return maxSum; } ``` ### 3. Product of Array Except Self ```typescript function productExceptSelf(nums: number[]): number[] { const n = nums.length; const left = new Array(n); const right = new Array(n); const result = new Array(n); // Left prefix products left[0] = 1; for (let i = 1; i < n; i++) { left[i] = left[i-1] * nums[i-1]; } // Right prefix products right[n-1] = 1; for (let i = n-2; i >= 0; i--) { right[i] = right[i+1] * nums[i+1]; } // Result is product of left and right for (let i = 0; i < n; i++) { result[i] = left[i] * right[i]; } return result; } ``` ## Time Complexity Analysis | Operation | Time Complexity | Space Complexity | Notes | |-----------|----------------|----------------|---------| | Build Prefix Sum | O(n) | O(n) | One pass through array | | Range Query | O(1) | O(1) | Constant time lookup | | 2D Prefix Sum | O(mn) | O(mn) | For m×n matrix | | 2D Range Query | O(1) | O(1) | Rectangle sum query | ## Best Practices ### 1. Handling Edge Cases ```typescript // Handle empty array if (arr.length === 0) return []; // Handle single element if (arr.length === 1) return [arr[0]]; // Handle negative numbers // Prefix sum works with negatives too ``` ### 2. Space Optimization ```typescript // In-place prefix sum (if input can be modified) for (let i = 1; i < arr.length; i++) { arr[i] += arr[i-1]; } ``` ### 3. Multiple Prefix Arrays ```typescript // For different operations, build separate prefix arrays const sumPrefix = buildPrefixSum(arr); const xorPrefix = buildPrefixXOR(arr); ``` ## Common Mistakes ### 1. Index Errors ```typescript // Wrong: off-by-one error prefix[i] = prefix[i] + arr[i]; // Should use i-1 // Correct: proper indexing prefix[i] = prefix[i-1] + arr[i]; ``` ### 2. Range Query Errors ```typescript // Wrong: incorrect range calculation sum = prefix[right] - prefix[left]; // Misses left element // Correct: proper range calculation sum = prefix[right] - prefix[left-1]; ``` ### 3. Integer Overflow ```typescript // Problem: Large numbers can cause overflow // Solution: Use BigInt or modulo operation const prefix = new Array(n).fill(BigInt(0)); ``` ### 4. Not Handling Negative Numbers ```typescript // Prefix sums work with negatives automatically // No special handling needed ``` ## Practice Problems ### Easy - [ ] Range sum query - [ ] Prefix sum array construction - [ ] Sum of subarray - [ ] Product of array except self - [ ] Find pivot index ### Medium - [ ] Subarray sum equals K - [ Maximum subarray (Kadane's algorithm) - [ ] Contiguous array - [ ] Find the shortest subarray - [ ] Minimum size subarray sum ### Hard - [ ] Maximum sum circular subarray - [ ] Subarray product less than K - [ ] Find longest subarray with equal 0s and 1s - [ ] 2D range sum queries - [ ] Range sum query 2D (immutable) ## Real-world Applications 1. **Data Analysis**: Time series analysis, moving averages 2. **Image Processing**: Integral images for fast feature computation 3. **Database Queries**: Range queries on indexed data 4. **Game Development**: Collision detection, spatial queries ## Advanced Techniques ### 1. Dynamic Programming with Prefix Sums ```typescript function countSubarrays(arr: number[], target: number): number { const prefixMap = new Map(); prefixMap.set(0, 1); let currentSum = 0; let count = 0; for (let i = 0; i < arr.length; i++) { currentSum += arr[i]; // Find how many times currentSum - target has occurred if (prefixMap.has(currentSum - target)) { count += prefixMap.get(currentSum - target); } // Update prefix map prefixMap.set(currentSum, (prefixMap.get(currentSum) || 0) + 1); } return count; } ``` ### 2. Sliding Window with Prefix Sums ```typescript function findMaxAverage(nums: number[], k: number): number { // Build prefix sum const prefix = new Array(nums.length); prefix[0] = nums[0]; for (let i = 1; i < nums.length; i++) { prefix[i] = prefix[i-1] + nums[i]; } let maxSum = prefix[k-1]; for (let i = k; i < nums.length; i++) { const currentSum = prefix[i] - prefix[i-k]; maxSum = Math.max(maxSum, currentSum); } return maxSum / k; } ``` ## Tips for Mastery ### 1. Understand the Mathematics - Prefix sum is cumulative addition - Range queries use the inclusion-exclusion principle - The technique works for any associative operation (sum, product, min, max) ### 2. Practice Different Operations - Sum, product, XOR, min, max - 1D and 2D applications - Dynamic programming combinations ### 3. Optimize Space - In-place modifications when possible - Single pass algorithms - Space-efficient data structures ### 4. Combine with Other Patterns - Sliding window - Binary search - Hash tables for frequency counting ## Next Steps 1. Master basic prefix sum construction and queries 2. Practice 1D and 2D applications 3. Learn to combine with dynamic programming 4. Apply to real-world problems --- **Key Takeaway**: Prefix sum is a fundamental technique for range queries and subarray problems. It transforms O(n) range queries into O(1) operations with O(n) preprocessing time. Always consider prefix sums when dealing with range-based array problems.