Files
algorithms/docs/01-foundations/prefix-sums.md
T

357 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<number, number>();
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.