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.
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
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.
Top-down: memoisation
Bottom-up: tabulation
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;}20365011074 20365011074 20365011074
The DP recipe
Define the state: what does one subproblem mean? (“ways[i] = number of ways to reach step i”, “best[a] = fewest coins for amount a”). Find the recurrence: how is a state built from smaller states? Thinking about the *last* decision usually works. Base cases: the smallest states, answered directly. Order: compute states so that dependencies are ready (usually increasing size). Answer: which state (or combination) is the final answer?
Fewest coins, done right
best[a] = 1 + min(best[a − c]) over all coins c ≤ a. Try it in the widget above with the second button.
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];}2 3 -1
| Top-down (memoisation) | Bottom-up (tabulation) | |
|---|---|---|
{ 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
Show the solution
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;}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.