docs(docs): add and expand algorithm documentation

This commit is contained in:
Prad Nukala committed 2026-08-20 16:04:24 -04:00
1 parent 34bb305a96
commit 32a41bdcb0
10 files changed
+1061 -50

No files matched your search

+86 -18
View File
@@ -3,30 +3,98 @@ title: 'Binary Search'
description: 'In algorithms, arrays and strings are very similar. They are both ordered collections of elements. The difference is that arrays are mutable, while strings are immutable.'
---
## The idea
Technically, an array can't be resized. A dynamic array, or list, can be. In the context of algorithm problems, usually when people talk about arrays, they are referring to dynamic arrays. In this entire course, we will be talking about dynamic arrays/lists, but we will just use the word "array".
You look for a name in a phone book. You do not start at page 1.
You open the middle. Too far? Go left. Not far enough? Go right.
Each step throws away half the book.
## Time Complexity
100 items → 7 steps. 4 billion items → 32 steps.
Arrays and strings are both ordered collections of elements. The difference is that arrays are mutable, while strings are immutable.
**The core rule:** ask a yes/no question that flips exactly once.
Everything left of the flip is "no". Everything right is "yes".
Binary search finds the flip point.
| Operation | Array/List | String(Immutable) |
| --------- | ---------- | ----------------- |
| Appending to end | *O(1) | O(n) |
| Popping from end | O(1) | O(n) |
| Insertion, not from end | O(n) | O(n) |
| Deletion, not from end | O(n) | O(n) |
| Modifying an element | O(1) | O(n) |
| Random access | *O(1) | O(n) |
| Checking if an element exists | O(1) | O(n) |
## The picture
:::note[Clarification 1]
Appending to the end of a list is amortized O(1) time complexity. This means that the time complexity of appending to the end of a list is the same as the time complexity of appending to the end of an array.
:::
```mermaid
flowchart TD
A["Array: 1 3 5 7 9 11 13<br/>Target: 9"] --> B["mid = 7<br/>7 < 9 → discard left half"]
B --> C["Array: 9 11 13<br/>mid = 11<br/>11 > 9 → discard right half"]
C --> D["Array: 9<br/>mid = 9<br/>Found ✓"]
:::note[Clarification 2]
Random access in this context means that you can access an element at any index in constant time.
:::
style D fill:#2e7d32,color:#fff
```
## The template
```python
def binary_search(nums: list[int], target: int) -> int:
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target:
return mid
if nums[mid] < target:
lo = mid + 1
else:
hi = mid - 1
return -1
```
## Search the answer space (LC 875 — Koko Eating Bananas)
The array does not need to exist. You can binary search over *possible answers*.
Ask: "does speed k work?" Slow speeds fail. Fast speeds work.
The answer flips once. Find the flip.
```mermaid
flowchart LR
A["k=1<br/>NO"] --> B["k=2<br/>NO"] --> C["k=3<br/>NO"] --> D["k=4<br/>YES ← answer"] --> E["k=5<br/>YES"] --> F["k=6<br/>YES"]
style D fill:#2e7d32,color:#fff
```
```python
import math
def min_eating_speed(piles: list[int], h: int) -> int:
def k_works(k: int) -> bool:
return sum(math.ceil(p / k) for p in piles) <= h
lo, hi = 1, max(piles)
while lo < hi:
mid = (lo + hi) // 2
if k_works(mid):
hi = mid # mid works — keep it, look left
else:
lo = mid + 1 # mid fails — discard it
return lo
```
## Not the same as two pointers
Two pointers **walks** — it inspects every element it passes. O(n).
Binary search **jumps** — it inspects one midpoint and discards half unseen. O(log n).
## Complexity
| | Time | Space |
|---|---|---|
| Sorted array | O(log n) | O(1) |
| Answer space | O(n log m) | O(1) |
n = array size, m = answer range.
## Close-out ritual
Before you submit, say out loud:
1. Time and space complexity.
2. One edge case trace: empty array, one element, or target not present.
## Plan problems
**A-set:** LC 704 · 74 · 875 · 33 · 153
**B-set:** LC 35 · 162 · 34 · 4 ⭐