AlgoPlusAlgoPlus
Learn/Problem-Solving Patterns
Lesson

Dynamic Programming

Solve each subproblem once, store it in a table, and reuse it — turning exponential recursion into a linear scan.

10 min read Watch it move Build it

Dynamic programming (DP) takes recursion's bad habit of re-solving the same small problems and fixes it: solve each subproblem *once*, save its answer in a table, and reuse it. Filling the table from the smallest cases upward turns work that would be exponential by naive recursion into a simple linear scan.

The two conditions

A problem is a DP candidate only when both of these hold:

  1. 1Optimal substructure — the best answer to the whole is built from best answers to its parts. The fewest coins for 6 equals 1 + (fewest coins for 6 − c) over each coin c.
  2. 2Overlapping subproblems — the same smaller problem (say dp[2]) is needed by many larger ones, so solving it once and storing it pays off. Without overlap, plain recursion is already fine.

Worked example — coin change

With coins [1, 3, 4], find the fewest coins that sum to 6. Let dp[a] be the fewest coins for amount a. Start from dp[0] = 0 and, for each amount, try every coin: dp[a] = 1 + min(dp[a − c]).

function coinChange(coins, amount) {
  const dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;                       // zero coins make amount 0
  for (let a = 1; a <= amount; a++) {
    for (const c of coins) {
      if (c <= a && dp[a - c] + 1 < dp[a]) {
        dp[a] = dp[a - c] + 1;     // use coin c, then best for the rest
      }
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

Filling dp left to right for coins [1, 3, 4] gives:

amount:  0   1   2   3   4   5   6
dp:      0   1   2   1   1   2   2

dp[6] = 2 — that's 3 + 3. Each entry reused answers already sitting to its left, so no work is ever repeated.

Why bottom-up just works
Every dp[a] depends only on *smaller* amounts. Filling the table from 0 upward guarantees the pieces you need are already computed — no recursion, no repeated work, no stack.
This is exactly where greedy breaks
Greedy would grab the biggest coin (4) for 6, then two 1s — three coins. The DP table finds 3 + 3, just two. When the greedy choice isn't provably safe, the table is what recovers the true optimum.
OperationTimeSpace
Coin change · one table entry per amountO(amount × coins)O(amount)
Check yourself
Which two properties must a problem have for dynamic programming to apply?