[AI] build roadmap for learning
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user