Phase 5 · Dynamic ProgrammingModule 16~40 min read

1-D Dynamic Programming

Master the one-dimensional DP patterns — Fibonacci, climbing stairs, house robber, coin change, and rod cutting.

What you'll learn

Most DP problems reduce to a handful of shapes. The simplest is 1-D DP, where the state is a single index into an array. Drill these patterns — coin change, house robber, rod cutting — and you'll spot them everywhere.

By the end you'll be able to:

  • Fill a 1-D DP table with multiple dependencies
  • Recognize the "take it or leave it" recurrence
  • Space-optimize a 1-D DP to a few variables

Coin change

Given coin denominations and a target amount, find the fewest coins that sum to it. The state dp[a] is the fewest coins for amount a; each coin c offers a path from dp[a - c]. Watch it build up, trying every coin for each amount:

Coin change (coins 1, 3, 4)
Coin change for 6
0
1
2
3
4
5
6
·
·
·
·
·
·
·

coins = {1, 3, 4} · dp[a] = 1 + min dp[a − c]

computing depends on answer
7 columns
1/15Goal: fewest coins to make every amount 0…6, using coins {1, 3, 4}.
dp[a] = 1 + the best reachable dp[a − c] over all coins c.

In code

Language
coin_change.py
def coin_change(coins, amount):
    INF = amount + 1
    dp = [0] + [INF] * amount        # 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] != INF else -1

Note

Notice this is where greedy failed (Module 13): for coins {1, 3, 4} and amount 6, greedy takes 4+1+1 = three coins, but DP finds 3+3 = two. DP considers every combination, so it can't be fooled.

House robber

Houses in a row each hold some loot, but you can't rob two adjacent houses. The recurrence is the classic "take it or leave it": dp[i] = max(dp[i-1], dp[i-2] + loot[i]) — either skip house i, or rob it and add the best from two houses back:

House robber
House robber
i
0
1
2
3
4
loot
2
7
9
3
1
dp
·
·
·
·
·

dp[i] = max(dp[i-1], dp[i-2] + loot[i])

computing depends on answer
5 columns
1/7Houses in a row — you can't rob two adjacent. Maximize the loot in the bottom row.
Each house: skip it (dp[i-1]) or rob it (dp[i-2] + loot[i]) — take the larger.
Language
house_robber.py
def rob(nums):
    prev2, prev1 = 0, 0
    for x in nums:
        # best if we skip x vs. rob x (plus best before last)
        prev2, prev1 = prev1, max(prev1, prev2 + x)
    return prev1

Tip

Because dp[i] needs only the previous two values, we keep just prev1 and prev2 — O(1) space. Spotting that a DP's window is small is the key to space optimization.

Rod cutting

A rod of length n can be cut into pieces, each length with a price. Maximize the total. The state dp[len] is the best revenue for a rod of that length, and the transition tries every first cut: dp[len] = max over k of (price[k] + dp[len - k]). It's the same "try every option" shape as coin change, maximizing instead of minimizing.

Recap & quick check

Key takeaways

  • 1-D DP uses a single index as the state.
  • Coin change: dp[a] = 1 + min over coins of dp[a - c].
  • House robber: dp[i] = max(dp[i-1], dp[i-2] + loot[i]) — take it or leave it.
  • DP beats greedy on coin change because it considers every combination.
  • When dp[i] needs only the last few values, reduce to O(1) space.

Quick check

1. In coin change, what is dp[a]?

2. The house robber recurrence dp[i] = max(dp[i-1], dp[i-2] + loot[i]) means:

3. Why can house robber use O(1) space?

4. Why does DP beat greedy for coin change with coins {1,3,4}, amount 6?

When the state needs two indices, we move to a 2-D table — starting with the famous knapsack. Next up: Module 17 — Knapsack & Subset-Sum DP.