346 lines
8.2 KiB
Markdown
346 lines
8.2 KiB
Markdown
# 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. |