Skip to main content
Data Structures & Algorithms · Chapters

ALGORITHMS / INTERMEDIATE

Dynamic programming: states and transitions

Define a useful state, derive a recurrence, and recover an optimal solution rather than just its score.

4 MIN READBY MYSMARTNOTESFREE TO READ
Reading text size
BY THE END

Turn repeated subproblems into a table whose entries have a precise meaning.

On this pageWrite the state in plain languageAsk what the last coin could have beenKeep the decision as well as the scoreRead a few entries before trusting the loopUnderstand the cost and the modelPractice: change one wordQuick referenceCheck your understandingSources

The hardest line in many dynamic-programming solutions is the one nobody writes: the sentence explaining what a table entry means.

Without that sentence, a recurrence looks like a pattern to memorise. With it, each update becomes a small claim you can check.

We will use a familiar problem with a useful trap. You have unlimited coins of denominations 1, 3 and 4. What is the fewest number of coins needed to make a target amount?

For 6, taking the largest available coin gives 4 + 1 + 1. Taking 3 + 3 is better. "Choose the largest" was an appealing rule, but it did not come with a proof.

Write the state in plain language​

Let best[s] mean the minimum number of coins needed to make exactly amount s.

That definition answers several questions before we write code. We are making an exact amount, so overshooting is not allowed. We want a minimum, so impossible states must never look cheaper than real ones. Zero needs zero coins.

Set best[0] = 0. Initially set every positive amount to infinity, meaning "no construction has been found". Infinity is useful here because adding a coin to an impossible construction should still be impossible.

MIT’s introductory dynamic-programming lecture develops the idea through reusable subproblems. Our coin example applies that habit to a different concrete state.

Ask what the last coin could have been​

Suppose an optimal construction of amount s ends with a coin c. Everything before that final coin must make s - c.

If that earlier construction were not optimal for its amount, replacing it with a better one would improve the full construction. So try every permitted final coin and keep the smallest result:

best[s] = minimum of (best[s - c] + 1)
over coins c where c <= s

Every dependency uses a smaller amount because our denominations are positive integers. This gives us an evaluation order: solve amounts from zero upwards.

The recurrence is not a command to copy a previous answer blindly. It is an exhaustive comparison of the possible final choices, made affordable by reusing the smaller answers.

Keep the decision as well as the score​

An answer of "two coins" is less useful than the coins themselves. Record which final coin improved each state, then walk backwards from the target.

function minimumCoins(coins, amount) {
if (!Number.isSafeInteger(amount) || amount < 0) {
throw new RangeError('amount must be a nonnegative safe integer');
}
if (coins.some(c => !Number.isSafeInteger(c) || c <= 0)) {
throw new RangeError('coins must be positive safe integers');
}

const best = Array(amount + 1).fill(Infinity);
const chosen = Array(amount + 1).fill(null);
best[0] = 0;

for (let sum = 1; sum <= amount; sum += 1) {
for (const coin of coins) {
if (coin <= sum && best[sum - coin] + 1 < best[sum]) {
best[sum] = best[sum - coin] + 1;
chosen[sum] = coin;
}
}
}

if (best[amount] === Infinity) return null;
const result = [];
for (let sum = amount; sum > 0; sum -= chosen[sum]) {
result.push(chosen[sum]);
}
return {count: best[amount], coins: result};
}

The input checks establish mathematical preconditions, not a production resource limit. A huge valid integer can still require an impossible allocation. A public service should impose a practical amount limit before allocating this table.

Read a few entries before trusting the loop​

For denominations [1, 3, 4], amounts 0 through 6 have minimum counts [0, 1, 2, 1, 1, 2, 2].

At amount 6, trying coin 1 gives best[5] + 1 = 3. Trying coin 3 gives best[3] + 1 = 2. Trying coin 4 gives best[2] + 1 = 3. The table chooses coin 3. Reconstructing follows 6 → 3 → 0.

For [4, 6] and amount 5, every candidate remains impossible, so the result is null. For amount zero, the answer is a valid empty construction, not a failure.

These cases test the meaning of the state. They are more informative than checking only an amount that contains a large denomination.

Understand the cost and the model​

With target A and k denominations, the loops do O(Ak) work and use O(A) table space, plus the returned coins. The algorithm is pseudo-polynomial: its cost depends on the numeric amount, which may be much larger than the number of bits needed to write that amount.

A top-down memoised version could solve the same states on demand. It would need a cache and a recursion-depth policy. CP-Algorithms’ DP introduction explains this relationship between memoisation and bottom-up evaluation.

Neither style rescues a poorly chosen state. First determine which information makes future decisions independent of the path used to reach that state.

Practice: change one word​

Suppose each coin can be used once. Why is our one-dimensional unlimited-coin recurrence no longer enough as written?

Work through the approach

The state must account for which coins are available. One explicit formulation is best[i][s]: the fewest coins making amount s using only the first i coins. Each row compares skipping coin i with taking it from the previous row.

A space-optimised version processes each coin and updates amounts in descending order. Descending order prevents a coin’s freshly updated states from being reused in the same pass. Updating upwards would allow the current coin to be used repeatedly, quietly changing the problem back.

Quick reference for this subject
When you seeConsiderRemember
Sorted boundaryBinary searchKeep a monotone division and shrink the unresolved interval.
Repeated lookupMap or setRemember a fact instead of rescanning earlier input.
Fewest equal-cost stepsBFSProcess discoveries in nondecreasing distance.
Repeated subproblemsDynamic programmingGive every state a meaning and a valid evaluation order.

For each guide, solve the practice variation before opening the explanation. If the code works but you cannot explain an update, stay with that example a little longer. Speed comes from understanding what can safely be ignored.

A QUICK CHECK

Does the idea hold?

For unlimited coins [1, 3, 4], how many coins make 6 optimally?

SOURCES & FURTHER READING

References for the concepts and guarantees discussed in this chapter.

How these guides are made

  1. Introduction to dynamic programming ↗
  2. MIT 6.006: dynamic programming and shortest paths ↗