<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Algorithm design
All lessons in this course

Lesson 42 of 50 15 minAdvanced

Dynamic programming: the idea

Recognise overlapping subproblems and optimal substructure, solve them top-down with memoisation or bottom-up with a table, and apply it to counting stairs, fewest coins and non-adjacent picks.

Dynamic programming (DP) is for problems where a recursive solution keeps solving the same smaller problems again and again. Instead of recomputing them, DP solves each one once and stores the answer. It often turns an exponential algorithm into a polynomial one. The name is historical and slightly misleading; “clever recursion with a cache” is closer.

Two signs that DP applies

  • Optimal substructure: the answer to the problem can be built from answers to smaller versions of the same problem.
  • Overlapping subproblems: those smaller problems are needed many times. (In merge sort they are not: each half is sorted once. That is divide and conquer, not DP.)

Example: climbing stairs

You climb n steps, taking 1 or 2 at a time. How many different ways are there? Think about the last move: you arrived either from step n − 1 (a single step) or from step n − 2 (a double step). So ways(n) = ways(n − 1) + ways(n − 2), with ways(0) = ways(1) = 1. Written as plain recursion this is the Fibonacci tree from the recursion lesson: about 1.6ⁿ calls.

Interactive

Filling a DP table

How many different ways to climb n steps, taking 1 or 2 at a time? Each answer is built from the two before it.

01
11
2
3
4
5
6
7
8
9
10
Cells computed 2, each once
Base cases: there is 1 way to stand still (0 steps) and 1 way to climb 1 step.

Top-down: memoisation

Keep the recursive code, but store each answer in a dictionary or array the first time it is computed. Every n is computed once: O(n).

Bottom-up: tabulation

Or skip recursion entirely: fill an array from the smallest cases upwards, so that every value you need is already there. No recursion means no stack overflow, and often the table can be shrunk: here only the last two values are ever needed.

Stairs.cs
var memo = new Dictionary<int, long>();Console.WriteLine(WaysMemo(50));Console.WriteLine(WaysTable(50));Console.WriteLine(WaysTwoVariables(50)); long WaysMemo(int n)                               // top-down{    if (n <= 1) return 1;    if (memo.TryGetValue(n, out long known)) return known;    return memo[n] = WaysMemo(n - 1) + WaysMemo(n - 2);} static long WaysTable(int n)                       // bottom-up{    var ways = new long[n + 1];    ways[0] = 1;    ways[1] = 1;    for (int i = 2; i <= n; i++)        ways[i] = ways[i - 1] + ways[i - 2];    return ways[n];} static long WaysTwoVariables(int n)                // bottom-up, O(1) memory{    long prev = 1, cur = 1;    for (int i = 2; i <= n; i++)        (prev, cur) = (cur, prev + cur);    return cur;}
Output
20365011074
20365011074
20365011074

The plain recursion for n = 50 would make about 40 billion calls. The DP versions do 49 additions.

The DP recipe

  1. Define the state: what does one subproblem mean? (“ways[i] = number of ways to reach step i”, “best[a] = fewest coins for amount a”).
  2. Find the recurrence: how is a state built from smaller states? Thinking about the *last* decision usually works.
  3. Base cases: the smallest states, answered directly.
  4. Order: compute states so that dependencies are ready (usually increasing size).
  5. Answer: which state (or combination) is the final answer?

Fewest coins, done right

The greedy lesson showed that “biggest coin first” fails for coins 1, 3 and 4. DP considers every possible last coin: best[a] = 1 + min(best[a − c]) over all coins c ≤ a. Try it in the widget above with the second button.

MinCoins.cs
Console.WriteLine(MinCoins(6, new[] { 1, 3, 4 }));    // 3 + 3Console.WriteLine(MinCoins(10, new[] { 1, 3, 4 }));   // 3 + 3 + 4Console.WriteLine(MinCoins(7, new[] { 2, 4 }));       // impossible static int MinCoins(int amount, int[] coins){    var best = new int[amount + 1];    Array.Fill(best, int.MaxValue);                    // "not possible yet"    best[0] = 0;    for (int a = 1; a <= amount; a++)        foreach (int c in coins)            if (c <= a && best[a - c] != int.MaxValue)                best[a] = Math.Min(best[a], best[a - c] + 1);   // c as the last coin    return best[amount] == int.MaxValue ? -1 : best[amount];}
Output
2
3
-1

Time O(amount × number of coins), memory O(amount). Compare with trying all combinations of coins, which grows exponentially with the amount.

Top-down (memoisation)Bottom-up (tabulation)
CodeRecursive, close to the recurrenceLoops, needs a fill order
ComputesOnly the states actually neededEvery state
RiskDeep recursion can overflow the stackNone
Memory tricksHardEasy to keep only the last row(s)
Quick check

Which problem is NOT a good fit for DP?

Quick check

ways[i] = ways[i − 1] + ways[i − 2]. In what order must a bottom-up loop fill the table?

Practice

Inspection sites along a road each have a value. You cannot inspect two neighbouring sites on the same day. Find the largest total value. { 2, 7, 9, 3, 1 } → 12 (2 + 9 + 1); { 5, 1, 1, 5 } → 10 (5 + 5). Write the recurrence, then a bottom-up loop with O(1) memory.

Show a hint

For site i you either skip it (best up to i − 1) or take it (its value + best up to i − 2). best[i] = max(best[i − 1], best[i − 2] + value[i]). Keep only the last two bests.

Show the solution
Solution.cs
Console.WriteLine(BestInspections(new[] { 2, 7, 9, 3, 1 }));Console.WriteLine(BestInspections(new[] { 5, 1, 1, 5 })); static int BestInspections(int[] value){    int beforePrev = 0, prev = 0;            // best up to i − 2 and up to i − 1    foreach (int v in value)    {        int cur = Math.Max(prev, beforePrev + v);   // skip this site, or take it        beforePrev = prev;        prev = cur;    }    return prev;}
Output
12
10

Remember

  • DP applies when the answer is built from smaller answers and those subproblems repeat.
  • Top-down memoisation caches a recursive solution; bottom-up tabulation fills a table in dependency order.
  • Recipe: state, recurrence (think about the last decision), base cases, order, answer.
  • DP fixes problems where greedy fails, such as fewest coins for any coin set.
0 of 50 lessons completed