Greed fails on 0/1 knapsack because the value of taking an item depends on what best fills the capacity it leaves, which is not known when the choice has to be made.
The obvious response is to stop choosing and try both branches, take the item or leave it, and keep whichever turns out better. Done directly that is subsets and hopeless. This lesson shows that it is not hopeless at all, because the recursion asks the same questions over and over, and that answering each question once turns an exponential algorithm into a polynomial one, at the cost of a table.
The same subproblem, again and again
The clearest demonstration needs no optimisation at all. Define with and , and compute it by writing exactly that as a recursive function.
Count the calls. The call tree for contains twice, three times, five times. In general the number of calls satisfies the Fibonacci recurrence itself, so it grows like . Computing makes 331,160,281 calls, about a third of a second on a modern machine. makes 40,730,022,147, forty seconds. would outlast the machine.
Yet there are only 101 distinct values in play. The algorithm is exponential purely because it recomputes the same answers, and this is the condition that dynamic programming exploits, called overlapping subproblems: the recursion visits a small set of distinct subproblems an enormous number of times.
Overlapping subproblems is not the same as the divide-and-conquer situation. Mergesort's two halves are disjoint and share nothing, so there is nothing to reuse and no table would help. Dynamic programming applies when the subproblems overlap and the problem has optimal substructure, the same property that licensed greedy recursion. Both are needed: overlap without optimal substructure gives nothing to combine, and optimal substructure without overlap is just divide and conquer.
There are two ways to exploit it. Memoisation keeps the recursion and adds a cache: before computing, look the subproblem up; after computing, store it. Bottom-up tabulation discards the recursion, works out the order in which subproblems depend on each other, and fills a table in that order with loops.
Memoisation is easier to write from a working recursion and computes only the subproblems actually reached. Tabulation avoids the call overhead, has predictable memory access, and often allows the table to be shrunk, since a row may depend only on the row above. Both turn into for Fibonacci: 39 additions rather than 331 million calls, a factor of eight million at .
Rod cutting
Now a real optimisation. A rod of length can be cut into whole-number pieces, and a piece of length sells for . Maximise the revenue.
There are ways to cut, one for each subset of the internal positions, so enumeration is out. The recursive structure is: the leftmost piece has some length between 1 and , and whatever it is, the rest of the rod should be cut optimally. So
with . That is optimal substructure stated directly, and the subproblems overlap heavily: is needed by , by , and so on. Filling upwards to costs , since computing examines options.
Example. Pieces of length 1 to 8 sell for 2, 5, 7, 8, 10, 17, 17 and 20. Fill the table and find the best revenue for a rod of length 8.
. . . . . . . .
So 22, from a piece of length 2 and a piece of length 6, worth . Note that selling the whole 8 fetches only 20, and that the greedy rule of taking the best price per unit length would pick length 6 at 2.83 per unit first, then length 2 at 2.50, reaching 22 here by luck; change to 16 and greed picks 6 then 2 for 21 while the optimum is as well. Greed is not reliably wrong, it is merely unproved.
Now you. With the same prices, what is if a length-9 piece sells for 24?
Answer
, which is . Five different decompositions tie at 24, including the uncut rod. Ties are common and harmless: the algorithm returns a maximum, and any decomposition attaining it is a correct answer.
0/1 knapsack
Now the problem greed could not solve. Items to with weights and values , a bag of capacity , each item taken whole or not at all.
The subproblem needs two parameters, and identifying them is the real work. Let be the best value obtainable from the first items with capacity . Item is either left out, giving , or taken, which is only possible if and gives . So
with and the second branch dropped when the item does not fit. The table has cells and each costs constant work, so the whole thing is .
Example. A bag holds 8 kg. The items are (3 kg, 4), (4 kg, 5), (2 kg, 3) and (5 kg, 6). Fill the table and find the best value.
Row 0 is all zeros. Row 1, with only the 3 kg item, is 0 for capacities 0 to 2 and 4 from capacity 3 onwards. Row 2 adds the 4 kg item: capacities 0 to 2 stay 0, capacity 3 stays 4, capacities 4 to 6 give 5, and capacity 7 gives , as does capacity 8. Row 3 adds the 2 kg item: capacity 2 becomes 3, capacity 3 stays 4, capacity 4 stays 5, capacity 5 becomes , capacity 6 becomes , and capacities 7 and 8 stay 9. Row 4 adds the 5 kg item and changes only capacity 8, to . The answer is 10.
Now you. A bag holds 9 kg, with items (2 kg, 3), (3 kg, 4), (4 kg, 5) and (5 kg, 6).
Answer
The final answer is 12. Working the last row: , which is , so the 5 kg item is left out. The first three items weigh exactly and are worth . Note that the ratio rule would have taken the 2 kg item first at 1.5 per kg, then the 3 kg at 1.33, then the 4 kg at 1.25, arriving at the same answer here; it is the previous lesson's instances, not all of them, where it goes wrong.
Reading the answer back
The table holds the value of the optimum, not the optimum itself, and the choices have to be recovered. Two ways.
Store a decision alongside each cell as it is filled, which costs memory but makes recovery a direct walk. Or reconstruct from the values alone, which costs nothing extra: start at and compare it with . If they differ, item must have been taken, so record it and move to ; if they agree, item was not needed, so move to . Repeat down to row 0.
On the first knapsack example: and , so item 4 was taken, leaving . and , so item 3 was not taken; nor was item 2, since . against , so item 1 was taken. The packing is items 1 and 4, weighing and worth .
Edit distance
The best-known table in this family compares two strings. The edit distance between them, from Vladimir Levenshtein in 1965, is the fewest single-character insertions, deletions and substitutions turning one into the other.
Let be the distance between the first characters of and the first of . Consider the last operation performed. It either deleted , costing ; or inserted , costing ; or aligned with , costing plus 1 if the characters differ and 0 if they match. Take the minimum of the three. The base cases are and , since an empty string needs one operation per character.
The table is cells filled in time, and only the previous row is ever needed, so the space can be cut to if the alignment itself is not wanted.
Example. What is the edit distance between "kitten" and "sitting"?
Filling the table row by row gives 3, and the path shows what the operations are: substitute k with s, substitute e with i, and insert g at the end. Spot-checking one interior cell, compares "kit" with "sit" and is 1, which is right: one substitution.
Now you. Compute the edit distance between "sunday" and "saturday".
Answer
- The route is "sunday" to "saunday" by inserting a, to "satunday" by inserting t, to "saturday" by substituting r for n. The table gives the count without finding that alignment first, which is the point of filling a table rather than searching for a sequence of edits: the search space of edit sequences is enormous and the table has cells.
Longest common subsequence is the same table with a different recurrence, and it is what diff computes: the unchanged lines are the longest common subsequence of the two files, and the added and removed lines are everything else. Sequence alignment in bioinformatics is the same table again, with a scoring matrix in place of the unit costs.
The cost, and the crack in it
The pattern is now visible. Identify the subproblem and its parameters, write the recurrence, decide the fill order, and the running time is the number of table cells times the work per cell. Rod cutting has cells at work each, so . Knapsack has cells at constant work, so . Edit distance has cells at constant work, so .
Two of those are honestly polynomial. The knapsack one is not, and the reason is the subject of the last lesson.
The size of an input is the number of bits needed to write it down. A capacity of is written in bits, so a table of cells is exponential in the length of that number. With 100 items and a capacity of one kilogram measured in grams, the table has cells and fills instantly. Measure the same capacity in micrograms and nothing about the problem has changed, but the table has cells. An algorithm polynomial in the numeric value of its input but exponential in its length is called pseudo-polynomial, and 0/1 knapsack has no algorithm known to be genuinely polynomial.
That is not a gap in this lesson. It is the edge of what anybody knows how to do, and the final lesson says what is on the other side of it.