[AI] build roadmap for learning
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
# 01: Array Basics
|
||||
|
||||
## Overview
|
||||
Array basics are the foundation of algorithmic thinking. Understanding how to traverse, manipulate, and analyze arrays is crucial for solving more complex problems.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### 1. Array Traversal
|
||||
**What it is**: Visiting each element in an array in sequence
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
// Linear traversal
|
||||
function linearSearch(arr: number[], target: number): number {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i] === target) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
```
|
||||
|
||||
**Important considerations**:
|
||||
- Always check if array is empty first
|
||||
- Use `for` loops for precise index control
|
||||
- Use `forEach` or `map` when you need the value but not the index
|
||||
|
||||
### 2. Basic Array Operations
|
||||
|
||||
#### Finding Minimum and Maximum
|
||||
```typescript
|
||||
function findMinMax(arr: number[]): { min: number, max: number } {
|
||||
if (arr.length === 0) {
|
||||
throw new Error("Array is empty");
|
||||
}
|
||||
|
||||
let min = arr[0];
|
||||
let max = arr[0];
|
||||
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
if (arr[i] < min) {
|
||||
min = arr[i];
|
||||
}
|
||||
if (arr[i] > max) {
|
||||
max = arr[i];
|
||||
}
|
||||
}
|
||||
|
||||
return { min, max };
|
||||
}
|
||||
```
|
||||
|
||||
**Time Complexity**: O(n) - We visit each element once
|
||||
**Space Complexity**: O(1) - We only store a few variables
|
||||
|
||||
#### Frequency Counting
|
||||
```typescript
|
||||
function frequencyCount(arr: number[]): Map<number, number> {
|
||||
const frequency = new Map<number, number>();
|
||||
|
||||
for (const num of arr) {
|
||||
frequency.set(num, (frequency.get(num) || 0) + 1);
|
||||
}
|
||||
|
||||
return frequency;
|
||||
}
|
||||
```
|
||||
|
||||
**Time Complexity**: O(n) - One pass through the array
|
||||
**Space Complexity**: O(n) - Store frequency of each unique element
|
||||
|
||||
### 3. Edge Cases to Consider
|
||||
|
||||
1. **Empty array**: What happens when `arr.length === 0`?
|
||||
2. **Single element**: Arrays with only one element
|
||||
3. **All same elements**: Arrays where all values are identical
|
||||
4. **Negative numbers**: How algorithms handle negative values
|
||||
5. **Large arrays**: Performance considerations
|
||||
|
||||
## Practice Patterns
|
||||
|
||||
### Pattern 1: Linear Search Variations
|
||||
**Problem**: Find all occurrences of a target
|
||||
```typescript
|
||||
function findAllOccurrences(arr: number[], target: number): number[] {
|
||||
const indices: number[] = [];
|
||||
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i] === target) {
|
||||
indices.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
return indices;
|
||||
}
|
||||
```
|
||||
|
||||
**Time Complexity**: O(n)
|
||||
**Space Complexity**: O(k) where k is number of occurrences
|
||||
|
||||
### Pattern 2: Array Validation
|
||||
**Problem**: Check if array meets certain criteria
|
||||
```typescript
|
||||
function isNonDecreasing(arr: number[]): boolean {
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
if (arr[i] < arr[i-1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
**Time Complexity**: O(n)
|
||||
**Space Complexity**: O(1)
|
||||
|
||||
## Real-world Applications
|
||||
|
||||
1. **Search functionality**: Finding items in a list
|
||||
2. **Data analysis**: Counting frequencies, finding trends
|
||||
3. **Game development**: Player positions, inventory management
|
||||
4. **Web development**: Form validation, data filtering
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
1. **Off-by-one errors**: Using `<` vs `<=` in loops
|
||||
2. **Undefined access**: Not checking array bounds
|
||||
3. **Memory leaks**: Not clearing temporary arrays
|
||||
4. **Inefficient algorithms**: Using nested loops when unnecessary
|
||||
|
||||
## Next Steps
|
||||
|
||||
After mastering array basics, you should be comfortable with:
|
||||
- Traversing arrays in different ways
|
||||
- Performing basic operations (search, count, min/max)
|
||||
- Handling edge cases properly
|
||||
- Understanding time complexity of basic operations
|
||||
|
||||
**Ready for**: Two pointers technique (next topic)
|
||||
@@ -0,0 +1,346 @@
|
||||
# Binary Search Pattern
|
||||
|
||||
## Overview
|
||||
Binary search is a fundamental algorithm for finding elements in sorted arrays. It follows the divide-and-conquer strategy, repeatedly dividing the search space in half.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Binary Search Principle
|
||||
- **Precondition**: Array must be sorted
|
||||
- **Process**: Compare target with middle element, eliminate half of search space
|
||||
- **Complexity**: O(log n) time, O(1) space
|
||||
|
||||
### Basic Template
|
||||
```typescript
|
||||
function binarySearch(arr: number[], target: number): number {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (arr[mid] === target) {
|
||||
return mid;
|
||||
} else if (arr[mid] < target) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return -1; // Target not found
|
||||
}
|
||||
```
|
||||
|
||||
## Variations
|
||||
|
||||
### 1. Lower Bound (First Occurrence)
|
||||
```typescript
|
||||
function lowerBound(arr: number[], target: number): number {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
let result = -1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (arr[mid] >= target) {
|
||||
result = mid;
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Upper Bound (Last Occurrence)
|
||||
```typescript
|
||||
function upperBound(arr: number[], target: number): number {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
let result = -1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (arr[mid] <= target) {
|
||||
result = mid;
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Find Closest Element
|
||||
```typescript
|
||||
function findClosest(arr: number[], target: number): number {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
|
||||
while (left < right - 1) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (arr[mid] < target) {
|
||||
left = mid;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
// Compare the two remaining elements
|
||||
return Math.abs(arr[left] - target) <= Math.abs(arr[right] - target)
|
||||
? arr[left] : arr[right];
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Applications
|
||||
|
||||
### 1. Search in Rotated Sorted Array
|
||||
```typescript
|
||||
function search(nums: number[], target: number): number {
|
||||
let left = 0;
|
||||
let right = nums.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (nums[mid] === target) {
|
||||
return mid;
|
||||
}
|
||||
|
||||
// Check if left half is sorted
|
||||
if (nums[left] <= nums[mid]) {
|
||||
if (nums[left] <= target && target < nums[mid]) {
|
||||
right = mid - 1;
|
||||
} else {
|
||||
left = mid + 1;
|
||||
}
|
||||
} else {
|
||||
// Right half is sorted
|
||||
if (nums[mid] < target && target <= nums[right]) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Find Minimum in Rotated Sorted Array
|
||||
```typescript
|
||||
function findMin(nums: number[]): number {
|
||||
let left = 0;
|
||||
let right = nums.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
|
||||
if (nums[mid] > nums[right]) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return nums[left];
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Search in 2D Matrix
|
||||
```typescript
|
||||
function searchMatrix(matrix: number[][], target: number): boolean {
|
||||
if (matrix.length === 0 || matrix[0].length === 0) return false;
|
||||
|
||||
const rows = matrix.length;
|
||||
const cols = matrix[0].length;
|
||||
|
||||
let left = 0;
|
||||
let right = rows * cols - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
const row = Math.floor(mid / cols);
|
||||
const col = mid % cols;
|
||||
|
||||
if (matrix[row][col] === target) {
|
||||
return true;
|
||||
} else if (matrix[row][col] < target) {
|
||||
left = mid + 1;
|
||||
} else {
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
## Time Complexity Analysis
|
||||
|
||||
| Problem | Time Complexity | Space Complexity | Notes |
|
||||
|---------|----------------|----------------|---------|
|
||||
| Basic Binary Search | O(log n) | O(1) | Standard implementation |
|
||||
| Lower Bound | O(log n) | O(1) | First occurrence |
|
||||
| Upper Bound | O(log n) | O(1) | Last occurrence |
|
||||
| Rotated Array | O(log n) | O(1) | Handle rotation |
|
||||
| 2D Matrix | O(log(mn)) | O(1) | Treat as 1D |
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Pointer Initialization
|
||||
- Use `left = 0` and `right = arr.length - 1`
|
||||
- For exclusive bounds: `left = 0` and `right = arr.length`
|
||||
|
||||
### 2. Mid Calculation
|
||||
```typescript
|
||||
// Avoid overflow in some languages
|
||||
const mid = left + Math.floor((right - left) / 2);
|
||||
|
||||
// Standard approach in TypeScript
|
||||
const mid = Math.floor((left + right) / 2);
|
||||
```
|
||||
|
||||
### 3. Loop Condition
|
||||
```typescript
|
||||
// Include equal case to find target
|
||||
while (left <= right)
|
||||
|
||||
// For exclusive bounds
|
||||
while (left < right)
|
||||
```
|
||||
|
||||
### 4. Pointer Updates
|
||||
```typescript
|
||||
// Always move mid pointer
|
||||
left = mid + 1; // Not mid
|
||||
right = mid - 1; // Not mid
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. Infinite Loop
|
||||
```typescript
|
||||
// Wrong: mid might not move
|
||||
left = mid; // Should be mid + 1
|
||||
right = mid; // Should be mid - 1
|
||||
|
||||
// Correct: move pointers past mid
|
||||
left = mid + 1;
|
||||
right = mid - 1;
|
||||
```
|
||||
|
||||
### 2. Incorrect Mid Calculation
|
||||
```typescript
|
||||
// Wrong: potential overflow
|
||||
const mid = (left + right) / 2;
|
||||
|
||||
// Correct: safer approach
|
||||
const mid = left + Math.floor((right - left) / 2);
|
||||
```
|
||||
|
||||
### 3. Wrong Loop Condition
|
||||
```typescript
|
||||
// Wrong: might miss element
|
||||
while (left < right) {
|
||||
// Target might be at right boundary
|
||||
|
||||
// Correct: include equal case
|
||||
while (left <= right) {
|
||||
// Ensures all elements are checked
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Not Handling Edge Cases
|
||||
```typescript
|
||||
// Wrong: empty array
|
||||
while (left <= right) {
|
||||
// Will fail for empty array
|
||||
|
||||
// Correct: add check
|
||||
if (arr.length === 0) return -1;
|
||||
```
|
||||
|
||||
## Practice Problems
|
||||
|
||||
### Easy
|
||||
- [ ] Basic binary search
|
||||
- [ ] Find lower bound
|
||||
- [ ] Find upper bound
|
||||
- [ ] Find closest element
|
||||
- [ ] Find peak in mountain array
|
||||
|
||||
### Medium
|
||||
- [ ] Search in rotated sorted array
|
||||
- [ ] Find minimum in rotated sorted array
|
||||
- [ ] Search in 2D matrix
|
||||
- [ ] Find first and last position of element
|
||||
- [ ] Find peak element
|
||||
|
||||
### Hard
|
||||
- [ ] Median of two sorted arrays
|
||||
- [ ] Find k-th element in sorted matrix
|
||||
- [ ] Find smallest letter greater than target
|
||||
- [ ] Find in mountain array
|
||||
- [ ] Find frequency of element in sorted array
|
||||
|
||||
## Real-world Applications
|
||||
|
||||
1. **Database Indexes**: B-tree searches
|
||||
2. **Game Development**: Collision detection, spatial partitioning
|
||||
3. **Machine Learning**: Decision trees, feature selection
|
||||
4. **Operating Systems**: Process scheduling, resource allocation
|
||||
|
||||
## Binary Search vs Linear Search
|
||||
|
||||
| Aspect | Binary Search | Linear Search |
|
||||
|--------|---------------|---------------|
|
||||
| Time Complexity | O(log n) | O(n) |
|
||||
| Space Complexity | O(1) | O(1) |
|
||||
| Precondition | Array must be sorted | No precondition |
|
||||
| Best for | Large sorted datasets | Small or unsorted datasets |
|
||||
| Insertion | O(n) | O(1) |
|
||||
|
||||
## Tips for Mastery
|
||||
|
||||
### 1. Understand the Pattern
|
||||
- Binary search works on any monotonic function
|
||||
- It's not just for arrays, but any searchable space
|
||||
- The key is reducing search space by half each time
|
||||
|
||||
### 2. Practice Edge Cases
|
||||
- Empty array
|
||||
- Single element
|
||||
- All elements same
|
||||
- Target not present
|
||||
- Target at boundaries
|
||||
|
||||
### 3. Implement Variations
|
||||
- Practice lower/upper bounds
|
||||
- Work with rotated arrays
|
||||
- Try 2D searches
|
||||
- Implement on custom data structures
|
||||
|
||||
### 4. Combine with Other Patterns
|
||||
- Binary search + two pointers
|
||||
- Binary search + sliding window
|
||||
- Binary search + dynamic programming
|
||||
|
||||
## Next Steps
|
||||
1. Master basic binary search (5+ problems)
|
||||
2. Practice rotated array variations
|
||||
3. Learn to apply binary search to search spaces
|
||||
4. Combine with other algorithmic patterns
|
||||
|
||||
---
|
||||
|
||||
**Key Takeaway**: Binary search is one of the most important algorithms. It's efficient, elegant, and applicable to many problems beyond simple array searches. Always consider binary search when dealing with sorted data.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,302 @@
|
||||
# Two Pointers Pattern
|
||||
|
||||
## Overview
|
||||
Two pointers is a powerful pattern for solving array and string problems efficiently. It involves using two pointers (indices) that traverse the data structure from different directions or at different speeds.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Types of Two Pointers
|
||||
1. **Opposite Direction**: One pointer starts at beginning, other at end
|
||||
2. **Same Direction**: Both pointers start at beginning, move at different speeds
|
||||
3. **Fast/Slow Pointers**: Detect cycles in linked lists
|
||||
|
||||
### Basic Template
|
||||
```typescript
|
||||
function twoPointers(arr: number[]): void {
|
||||
let left = 0;
|
||||
let right = arr.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
// Process elements at left and right
|
||||
const leftVal = arr[left];
|
||||
const rightVal = arr[right];
|
||||
|
||||
// Move pointers based on condition
|
||||
if (condition) {
|
||||
left++;
|
||||
} else {
|
||||
right--;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Sorted Arrays
|
||||
Perfect for problems where the array is sorted and you need to find pairs, triplets, or perform operations.
|
||||
|
||||
### 2. Palindrome Checking
|
||||
Check if a string or array reads the same forwards and backwards.
|
||||
|
||||
### 3. Container Problems
|
||||
Find maximum area, minimum window, or similar container-based problems.
|
||||
|
||||
### 4. Subarray Problems
|
||||
Find subarrays that meet certain conditions.
|
||||
|
||||
## Example Implementations
|
||||
|
||||
### Two Sum (Sorted Array)
|
||||
```typescript
|
||||
function twoSum(numbers: number[], target: number): number[] {
|
||||
let left = 0;
|
||||
let right = numbers.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const sum = numbers[left] + numbers[right];
|
||||
if (sum === target) {
|
||||
return [left + 1, right + 1]; // 1-based indexing
|
||||
} else if (sum > target) {
|
||||
right--;
|
||||
} else {
|
||||
left++;
|
||||
}
|
||||
}
|
||||
|
||||
return [-1, -1];
|
||||
}
|
||||
```
|
||||
|
||||
### Container With Most Water
|
||||
```typescript
|
||||
function maxArea(height: number[]): number {
|
||||
let left = 0;
|
||||
let right = height.length - 1;
|
||||
let maxArea = 0;
|
||||
|
||||
while (left < right) {
|
||||
const currentHeight = Math.min(height[left], height[right]);
|
||||
const currentWidth = right - left;
|
||||
const currentArea = currentHeight * currentWidth;
|
||||
|
||||
maxArea = Math.max(maxArea, currentArea);
|
||||
|
||||
if (height[left] < height[right]) {
|
||||
left++;
|
||||
} else {
|
||||
right--;
|
||||
}
|
||||
}
|
||||
|
||||
return maxArea;
|
||||
}
|
||||
```
|
||||
|
||||
### Sorted Squares
|
||||
```typescript
|
||||
function sortedSquares(nums: number[]): number[] {
|
||||
const result = new Array(nums.length);
|
||||
let left = 0;
|
||||
let right = nums.length - 1;
|
||||
let index = nums.length - 1;
|
||||
|
||||
while (left <= right) {
|
||||
const leftSquare = nums[left] * nums[left];
|
||||
const rightSquare = nums[right] * nums[right];
|
||||
|
||||
if (leftSquare > rightSquare) {
|
||||
result[index] = leftSquare;
|
||||
left++;
|
||||
} else {
|
||||
result[index] = rightSquare;
|
||||
right--;
|
||||
}
|
||||
index--;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### Palindrome Check
|
||||
```typescript
|
||||
function isPalindrome(s: string): boolean {
|
||||
let left = 0;
|
||||
let right = s.length - 1;
|
||||
|
||||
while (left < right) {
|
||||
if (s[left] !== s[right]) {
|
||||
return false;
|
||||
}
|
||||
left++;
|
||||
right--;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
## Time Complexity Analysis
|
||||
|
||||
| Problem | Time Complexity | Space Complexity | Pattern |
|
||||
|---------|----------------|----------------|---------|
|
||||
| Two Sum | O(n) | O(1) | Opposite direction |
|
||||
| Max Area | O(n) | O(1) | Opposite direction |
|
||||
| Sorted Squares | O(n) | O(n) | Opposite direction |
|
||||
| Palindrome | O(n) | O(1) | Opposite direction |
|
||||
|
||||
## Variations
|
||||
|
||||
### Fast/Slow Pointers (Cycle Detection)
|
||||
```typescript
|
||||
function hasCycle(head: ListNode): boolean {
|
||||
let slow = head;
|
||||
let fast = head;
|
||||
|
||||
while (fast && fast.next) {
|
||||
slow = slow.next;
|
||||
fast = fast.next.next;
|
||||
|
||||
if (slow === fast) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### Sliding Window (Two Pointers Same Direction)
|
||||
```typescript
|
||||
function maxSlidingWindow(nums: number[], k: number): number[] {
|
||||
const result = [];
|
||||
const deque = [];
|
||||
|
||||
for (let i = 0; i < nums.length; i++) {
|
||||
// Remove elements outside window
|
||||
while (deque.length > 0 && deque[0] <= i - k) {
|
||||
deque.shift();
|
||||
}
|
||||
|
||||
// Remove elements smaller than current
|
||||
while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
|
||||
deque.pop();
|
||||
}
|
||||
|
||||
deque.push(i);
|
||||
|
||||
// Add maximum to result
|
||||
if (i >= k - 1) {
|
||||
result.push(nums[deque[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Pointer Initialization
|
||||
- **Opposite Direction**: `left = 0, right = arr.length - 1`
|
||||
- **Same Direction**: `left = 0, right = 0` or `left = 0, right = 1`
|
||||
- **Fast/Slow**: `slow = head, fast = head`
|
||||
|
||||
### 2. Loop Conditions
|
||||
- **Opposite Direction**: `while (left < right)`
|
||||
- **Same Direction**: `while (right < arr.length)`
|
||||
- **Fast/Slow**: `while (fast && fast.next)`
|
||||
|
||||
### 3. Movement Logic
|
||||
- **For sorted arrays**: Move pointer based on comparison with target
|
||||
- **For containers**: Move pointer pointing to smaller element
|
||||
- **For palindromes**: Move both pointers towards center
|
||||
|
||||
### 4. Edge Cases
|
||||
- Empty array/single element
|
||||
- All elements same
|
||||
- Array with negative numbers
|
||||
- Array with very large values
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. Incorrect Loop Condition
|
||||
```typescript
|
||||
// Wrong: includes left === right case
|
||||
while (left <= right) {
|
||||
// This might process the same element twice
|
||||
}
|
||||
|
||||
// Correct: for opposite direction
|
||||
while (left < right) {
|
||||
// Ensures elements are distinct
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Wrong Pointer Movement
|
||||
```typescript
|
||||
// Wrong: always move left
|
||||
if (sum > target) {
|
||||
left++; // Should move right
|
||||
} else {
|
||||
left++;
|
||||
}
|
||||
|
||||
// Correct: move appropriate pointer
|
||||
if (sum > target) {
|
||||
right--;
|
||||
} else {
|
||||
left++;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Ignoring Edge Cases
|
||||
```typescript
|
||||
// Wrong: no empty array check
|
||||
while (left < right) {
|
||||
// Will fail for empty array
|
||||
|
||||
// Correct: add edge case check
|
||||
if (arr.length === 0) return [];
|
||||
```
|
||||
|
||||
## Practice Problems
|
||||
|
||||
### Easy
|
||||
- [ ] Two Sum (sorted array)
|
||||
- [ ] Palindrome check
|
||||
- [ ] Valid palindrome II
|
||||
- [ ] Squares of sorted array
|
||||
- [ ] Reverse string
|
||||
|
||||
### Medium
|
||||
- [ ] Container with most water
|
||||
- [ ] Three sum
|
||||
- [ ] Remove duplicates from sorted array
|
||||
- [ ] Minimum size subarray sum
|
||||
- [ ] Trapping rain water
|
||||
|
||||
### Hard
|
||||
- [ ] Longest substring without repeating characters
|
||||
- [ ] Substring with concatenation of all words
|
||||
- [ ] Minimum window substring
|
||||
- [ ] Shortest unsorted continuous subarray
|
||||
- [ ] Longest valid parentheses
|
||||
|
||||
## Real-world Applications
|
||||
|
||||
1. **Image Processing**: Edge detection, feature matching
|
||||
2. **Data Analysis**: Time series analysis, moving averages
|
||||
3. **Text Processing**: String matching, pattern recognition
|
||||
4. **Signal Processing**: Filter design, peak detection
|
||||
|
||||
## Next Steps
|
||||
1. Master two pointers pattern with 10+ problems
|
||||
2. Practice with different array types (sorted, unsorted)
|
||||
3. Learn sliding window variation
|
||||
4. Combine with other patterns (hash tables, binary search)
|
||||
|
||||
---
|
||||
|
||||
**Key Takeaway**: Two pointers is often the optimal solution for array problems, especially when dealing with sorted data or pairs of elements. Always consider this pattern when you see "sorted array" or "find pairs" in problem statements.
|
||||
Reference in New Issue
Block a user