Everyone Can Learn Program
中文 Start learning
Core · AlgorithmsLesson 8 of 8Free to read

Dynamic programming · Data Structures and Algorithms

How do you stop solving the same small problem over and over?

Course contents

Why you need it

Some recursive solutions repeat the same calls millions of times. Dynamic programming (DP) saves each small answer once and reuses it, turning exponential time into linear. It's behind route planners, spell checkers and many of the harder interview questions.

The intuition

Climbing stairs taking 1 or 2 steps at a time: the ways to reach step n = ways(n − 1) + ways(n − 2). Plain recursion recomputes ways(3) again and again; the call count roughly doubles with each step.

Two fixes:

  • Memoisation (top-down): keep the recursion, but store answers in a dict. Python's @functools.lru_cache does it for you.
  • Tabulation (bottom-up): fill a list from the smallest case up: ways[i] = ways[i-1] + ways[i-2].

The hard part is the first step: define what one small answer means ("ways[i] = number of ways to reach step i"), and how it's built from smaller ones.

Definition

Dynamic programming

Solving a problem by combining answers to overlapping subproblems, computing each subproblem once.

Memoisation

Caching a function's results by its arguments, so repeated calls return instantly.

Examples

Count the repeated work

Try it yourself

Plain recursion versus memoised recursion, counting calls.

calls = 0

def ways(n):
    global calls
    calls += 1
    if n <= 1:
        return 1
    return ways(n - 1) + ways(n - 2)

print(ways(20), calls)

memo = {}
calls = 0

def ways_memo(n):
    global calls
    calls += 1
    if n <= 1:
        return 1
    if n not in memo:
        memo[n] = ways_memo(n - 1) + ways_memo(n - 2)
    return memo[n]

print(ways_memo(20), calls)
Step by step
  1. L8Two calls per call: the count explodes.
  2. L1010946 ways, 21891 calls.10946 21891
  3. L20Each n is computed once.
  4. L24Same answer, 39 calls.10946 39

Fewest coins, bottom-up

Try it yourself

best[a] is the fewest coins that make amount a. Fill it from 0 upward.

def fewest_coins(coins, amount):
    INF = amount + 1
    best = [0] + [INF] * amount
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a and best[a - c] + 1 < best[a]:
                best[a] = best[a - c] + 1
    return best[amount] if best[amount] != INF else -1

print(fewest_coins([1, 3, 4], 6))
print(fewest_coins([5, 10], 3))
Step by step
  1. L30 coins make amount 0. INF means "not possible yet".
  2. L6Use coin c on top of the best way to make a − c.
  3. L103 + 3: two coins. (Greedy 4 + 1 + 1 would use three.)2
  4. L11No mix of 5 and 10 makes 3.-1

Common mistakes

Greedy instead of DP

Always taking the biggest coin fails for coins [1, 3, 4] and amount 6. When a choice affects later choices, check DP.

A vague state

If you can't say in one sentence what best[i] means, the recurrence will be wrong. Write the sentence first.

Quick quiz

1. What does this print?

ways = [1, 1]
for i in range(2, 6):
    ways.append(ways[i - 1] + ways[i - 2])
print(ways)
  1. A
    [1, 1, 2, 3, 5, 8]
  2. B
    [1, 1, 2, 4, 8, 16]
  3. C
    [1, 1, 2, 3, 5]
Show the answer

Answer: A

Each entry is the sum of the two before it.

2. What makes a problem a good fit for dynamic programming?

  1. AThe same subproblems repeat, and the best answer is built from best sub-answers
  2. BThe input is sorted
  3. CIt needs a queue
Show the answer

Answer: A

Overlapping subproblems plus optimal substructure: that's when caching pays off.

Practice

3 exercises in three steps: predict what a program prints, change a program to meet a new requirement, then write one from scratch. Your code runs in the browser and is checked the moment you submit.

  1. PredictFill the table

    Type exactly what this program prints.

  2. ModifyMemoise it

    grid_paths(r, c) counts the paths from the top-left to the bottom-right of an r × c grid, moving only right or down. It's correct but exponential. Add a memo dict (or functools.lru_cache) so the speed test passes.

  3. WriteFewest coins

    Write fewest_coins(coins, amount) that returns the fewest coins that add up to amount, or -1 if it can't be done. Build a table best from 0 up to amount.

Practise this lesson

Open the interactive lesson to edit and run these examples and solve the exercises with instant feedback. Free to start, no sign-up.

Interactive lessons open in order. If you have not done the earlier ones yet, you start at the course map.

Terms in this lesson

TermChineseDefinition
Dynamic programming动态规划Reusing answers to overlapping subproblems instead of recomputing them.
Memoisation记忆化Caching a function's results by its arguments.