Files
leetcode/docs/(algorithms)/dynamic-programming.mdx
T

127 lines
3.1 KiB
Plaintext
Raw Normal View History

---
title: Dynamic Programming (1-D)
---
# Dynamic Programming (1-D)
## The idea
DP is backtracking with a memory.
Backtracking tries every path. Many paths repeat the same subproblem.
DP solves each subproblem **once**, saves the answer, and reuses it.
Grokking's rule: break the big problem into small problems.
Solve the small ones first. Build up.
**The two things you must find:**
1. **The state** — what does `dp[i]` mean, in one sentence?
2. **The recurrence** — how does `dp[i]` come from earlier answers?
## The picture — why memo matters (Climbing Stairs)
Without memo, `f(5)` computes `f(3)` twice and `f(2)` three times:
```mermaid
flowchart TD
A["f(5)"] --> B["f(4)"]
A --> C["f(3)"]
B --> D["f(3)"]
B --> E["f(2)"]
C --> F["f(2)"]
C --> G["f(1)"]
D --> H["f(2)"]
D --> I["f(1)"]
style C fill:#c62828,color:#fff
style D fill:#c62828,color:#fff
style E fill:#ef6c00,color:#fff
style F fill:#ef6c00,color:#fff
style H fill:#ef6c00,color:#fff
```
Red and orange = repeated work. Memo turns the tree into a straight line: O(2ⁿ) → O(n).
## Climbing Stairs (LC 70)
State: `dp[i]` = ways to reach step i.
Recurrence: you arrive from one step below or two below.
```python
def climb_stairs(n: int) -> int:
if n <= 2:
return n
prev2, prev1 = 1, 2
for _ in range(3, n + 1):
prev2, prev1 = prev1, prev1 + prev2
return prev1
```
## House Robber (LC 198)
State: `dp[i]` = max loot using houses 0..i.
Recurrence at each house: **rob it** (skip the neighbor) or **skip it**.
```mermaid
flowchart LR
A["House i"] --> B["Rob:<br/>nums[i] + dp[i-2]"]
A --> C["Skip:<br/>dp[i-1]"]
B --> D["dp[i] = max of both"]
C --> D
style D fill:#2e7d32,color:#fff
```
```python
def rob(nums: list[int]) -> int:
skip = take = 0
for n in nums:
skip, take = max(skip, take), skip + n # skip it / rob it
return max(skip, take)
```
## Coin Change (LC 322)
State: `dp[a]` = fewest coins to make amount a.
Recurrence: try each coin, take the best.
```python
import math
def coin_change(coins: list[int], amount: int) -> int:
dp = [math.inf] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for c in coins:
if c <= a:
dp[a] = min(dp[a], dp[a - c] + 1)
return dp[amount] if dp[amount] != math.inf else -1
```
## The interview script
Say these four lines out loud, in order:
1. "The brute force is backtracking — try everything."
2. "Subproblems overlap, so I will memoize."
3. "State: dp[i] means ___." (one sentence)
4. "Recurrence: dp[i] = ___."
## Complexity
| | Time | Space |
|---|---|---|
| Climbing Stairs / House Robber | O(n) | O(1) with rolling vars |
| Coin Change | O(amount × coins) | O(amount) |
## Close-out ritual
Before you submit, say out loud:
1. Time and space complexity.
2. One edge case trace: n = 0 or 1, empty array, or unreachable amount (return -1).
## Plan problems
**A-set:** LC 70 · 746 · 198 · 213 · 322 · 300
**B-set:** LC 139 · 91 · 647
Note: DP matters only if Google or Databricks advance to later rounds. Your fintech targets skew toward simulation, hashmap, and heap.