Divide and Conquer, Greedy, and Dynamic Programming
Dynamic Programming, and the Size of the Table
A dynamic program is a table plus a recurrence, and its complexity is almost always (number of cells) × (work per cell). That single sentence answers most complexity questions in this topic without any analysis: the LCS of strings of length m and n has an m × n table filled in O(1) each, so O(mn); 0/1 knapsack has an n × W table, so O(nW); matrix-chain multiplication has an n × n table whose cells each try up to n split points, so O(n³).
One worked figure to anchor the LCS: the longest common subsequence of AGGTAB and GXTXAYB is GTAB, of length 4. Note that a subsequence need not be contiguous — that is what separates it from a substring, and confusing the two turns an O(mn) dynamic program into a different and easier problem.
Greedy Where It Is Provable: Huffman
Huffman coding is the greedy algorithm with the cleanest proof: repeatedly merge the two least frequent symbols, and the result is an optimal prefix code. Take frequencies 5, 9, 12, 13, 16 and 45. The merges cost 14, 25, 30, 44 and 100, summing to 224, which is also the total encoded length — and the two agree because every merge adds one bit to every symbol beneath it.
| Frequency | Code length | Contribution |
|---|---|---|
| 45 | 1 | 45 |
| 16 | 3 | 48 |
| 13 | 3 | 39 |
| 12 | 3 | 36 |
| 9 | 4 | 36 |
| 5 | 4 | 20 |
| Total | — | 224 bits |
Key takeaways
- Divide and conquer needs non-overlapping subproblems; dynamic programming exists because they overlap.
- Greedy needs a provable greedy-choice property — it is sometimes false and always plausible.
- Greedy by ratio is optimal for fractional knapsack and wrong for 0/1: 240 against 220 optimal and 160 greedy.
- A dynamic program costs (number of cells) × (work per cell): LCS O(mn), knapsack O(nW), matrix chain O(n³).
- O(nW) is pseudo-polynomial — W is a value, not a size — so it does not contradict NP-hardness.
- A subsequence need not be contiguous; a substring must be.
- Huffman merges the two least frequent symbols; on 5, 9, 12, 13, 16, 45 the total is 224 bits.
- The most frequent symbol gets the shortest code, and the Huffman tree is not unique though its total is.
Practice questions (8)
Attempt each one before opening the answer. Every explanation names the tempting wrong option as well as the right one, because that is where marks are lost.
A 0/1 knapsack has capacity 50 and items of weights 10, 20, 30 with values 60, 100, 120. What is the maximum total value?
Numerical answer — type the value.
Show answer
Answer: 220
The best 0/1 choice is the 20 and the 30, filling the capacity exactly for 100 + 120 = 220. Greedy by value-per-weight would take the 10 (ratio 6) and the 20 (ratio 5), reach weight 30, and be unable to fit the 30 — stopping at 160. And the fractional answer is 240, taking two-thirds of the last item. Three different numbers for one item list, and which is correct depends entirely on whether items may be split. Reading that from the question before computing anything is the whole skill; 240 is the answer to a question that was not asked.Greedy by value-per-unit-weight is provably optimal for:
Show answer
Answer: B — The fractional knapsack
Only the fractional version. The proof depends on being able to fill any leftover capacity with a fraction of the next-best item, so no capacity is ever wasted — remove divisibility and the argument collapses, because a leftover gap may fit nothing at all. The instance above shows the damage: greedy gets 160 where 220 is available. Option C is the answer a candidate gives from having seen greedy work on the fractional case and generalising, which is precisely the habit this pairing exists to break. 0/1 needs dynamic programming, at O(nW).Symbols have frequencies 5, 9, 12, 13, 16 and 45. What is the total length in bits of the optimal Huffman encoding?
Numerical answer — type the value.
Show answer
Answer: 224
Merging the two least frequent each time gives merges of 14 (5+9), 25 (12+13), 30 (14+16), 44 (25+30… by the queue order) and finally 100, and the sum of the merge costs is 224 — which is the total encoded length, because each merge adds one bit to every symbol under it. Checking it the other way agrees: the code lengths come out 1, 3, 3, 3, 4, 4 for 45, 16, 13, 12, 9, 5, and 45×1 + 16×3 + 13×3 + 12×3 + 9×4 + 5×4 = 224. Computing it both ways is the reliable habit here, since the merge-sum method is fast and the length method catches a mis-built tree.The 0/1 knapsack dynamic program runs in O(nW). Since 0/1 knapsack is NP-hard, this means:
Show answer
Answer: B — The bound is pseudo-polynomial, since W is a value and not an input size
Writing the capacity W down takes about log₂W bits, so an algorithm whose running time is proportional to W is exponential in the length of that input. It is called pseudo-polynomial, and it resolves the apparent paradox without disturbing anything: the algorithm is correct, the problem is still NP-hard, and there is no claim about P versus NP. The practical reading is the useful one — the table method is fine for a capacity of a few thousand and hopeless for a 64-bit capacity, and that is a statement about the numbers in the instance rather than about how many items there are.What is the length of the longest common subsequence of AGGTAB and GXTXAYB?
Numerical answer — type the value.
Show answer
Answer: 4
The LCS is GTAB, length 4. The characters need not be adjacent in either string — that is what makes it a subsequence rather than a substring, and a candidate who looks for a contiguous match finds only length 1 or 2. The dynamic program is an m × n table where a cell is 1 + the diagonal when the characters match and otherwise the larger of its two neighbours, so the cost is O(mn) — here 6 × 7 = 42 cells. The subsequence-versus-substring distinction is worth reading for explicitly, because the two problems have different answers and different algorithms.Which of the following are true of an optimal Huffman code? (More than one may be correct.)
Show answer
Answer: A — The most frequent symbol never has a longer code than a less frequent one; B — No codeword is a prefix of another
A and B. A is what optimality means here — if a rarer symbol had a shorter code, swapping the two would reduce the total, so no optimal code can have that shape, and it lets you reject a proposed assignment on sight. B is the prefix-free property, which is what makes the code decodable without separators and is guaranteed because every symbol sits at a leaf. C is false: ties in the priority queue can break either way, so several distinct trees achieve the same total — 224 here — which is why a question asking for the total is well posed and one asking for a particular codeword may not be. D describes a fixed-length code, which is the thing Huffman improves on.Matrix-chain multiplication is solved by dynamic programming in:
Show answer
Answer: B — O(n³)
O(n³), straight from the rule that a dynamic program costs cells × work per cell: the table is n × n over subchains, and filling one cell tries every split point inside that subchain, which is up to n of them. Option C is the cost of enumerating all parenthesisations, which is Catalan-many and exactly what the table exists to avoid. This is also the standard demonstration that the order of multiplication matters without changing the answer — the product is the same, the number of scalar multiplications is not, which is why the problem is worth solving at all.Dynamic programming is preferable to plain divide-and-conquer exactly when:
Show answer
Answer: B — The subproblems overlap, so the same one is solved repeatedly
Overlapping subproblems is the whole condition, and it is the difference the two techniques turn on. Merge sort's halves are disjoint, so storing results would buy nothing — there is never a repeat. Fibonacci and LCS revisit the same subproblem many times, so storing collapses an exponential recursion to a polynomial table. The second condition dynamic programming needs is optimal substructure, that an optimal solution is built from optimal solutions to subproblems; without it the table would be storing answers that cannot be combined. Option A is irrelevant — plenty of dynamic programs maximise, and plenty of divide-and-conquer algorithms optimise nothing at all.