Rod Cutting is the classic introduction to bottom-up dynamic programming. You are given a rod and a price for every possible piece length, and you must chop it into pieces that earn the most money. The trick is that the best way to cut a long rod is built from the best ways to cut shorter rods.
Problem. A rod of length n can be cut into integer-length pieces. A piece of length c sells for
price[c]. Cut the rod (or leave it whole) to maximize total revenue. Return that maximum.
Example: price = [1, 5, 8, 9] (so length 1 → 1, length 2 → 5, length 3 → 8, length 4 → 9), n = 4.
The best is to cut it into two pieces of length 2, earning 5 + 5 = 10 — better than selling it
whole for 9.
The slow way first
The brute-force idea: try every possible way to chop the rod. The first cut could be at length 1, 2, 3, …, and then you recursively cut whatever is left. That branches into an exponential number of combinations — O(2ⁿ) — and re-solves the same sub-rods over and over.
The question to ask: what do I actually need to decide? Only the first cut. If I sell a leading piece of length c, the rest is a rod of length L − c, and the best revenue for that is a smaller version of the exact same problem. So if I already knew the answer for every shorter rod, the first cut would be a single max over the choices.
The idea: build short rods first
Define dp[L] = the best revenue for a rod of length L. A rod of length 0 earns nothing, so dp[0] = 0. For any longer L, try every first cut c from 1 to L:
dp[L] = max over c of ( price[c] + dp[L - c] )
Each candidate is the money from the leading piece (price[c]) plus the best we can already do with the remainder (dp[L - c], which we computed earlier). Fill dp left to right so every dp[L - c] it reads is already final.
The key insight: we never re-solve a sub-rod. Because dp is filled in increasing order of length, dp[L − c] is always a finished answer by the time dp[L] needs it.
Walk through it
Step through the animation. The top strip is price; the bottom strip is dp. The pointer L moves left to right. For each L, watch one price[c] cell and the dp[L − c] cell it reads light up — that is the winning cut — and the result drops into dp[L]. When L reaches 4, the best cut is c = 2: price[2] + dp[2] = 5 + 5 = 10.
Pseudocode
dp[0] = 0 # an empty rod earns nothing
for L from 1 to n: # solve every length, short to long
best = 0
for c from 1 to L: # try every first cut
candidate = price[c] + dp[L - c]
best = max(best, candidate)
dp[L] = best # lock in the best for length L
return dp[n] # the answer for the full rodThe Python solution
def rod_cutting(price, n):
dp = [0] * (n + 1)
for L in range(1, n + 1):
best = 0
for c in range(1, L + 1):
cand = price[c - 1] + dp[L - c]
best = max(best, cand)
dp[L] = best
return dp[n]dp[L]holds the best revenue for a rod of lengthL;dp[0]stays 0 (an empty rod earns nothing).- The outer loop fills
dpfrom short rods to long ones, so every value it reads is already final. - The inner loop tries every first cut
c. We useprice[c - 1]because the list is 0-indexed but lengths start at 1. - Lines 6 and 7 are the heart: take
price[c]for the leading piece plusdp[L - c]for the remainder, and keep the best. dp[n]is the answer for the full rod.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (every cut combo) | O(2ⁿ) (moderate) | re-solves the same sub-rods |
| Bottom-up DP (this solution) | O(n²) (slow) | n lengths, up to n cuts each |
O(n) (moderate)We trade O(n) space (the dp array) to collapse an exponential search into two nested loops. That move — define a sub-answer, fill it in order, and reuse it — is the core of dynamic programming.
When this pattern shows up
Whenever a problem asks for the best way to split one thing into pieces — cutting a rod, breaking a
string into words, partitioning an array — define dp[i] for the first i units and try every place to
make the first split. Rod Cutting, Word Break, and Coin Change are all this same shape.
Mind the indexing. Lengths run from 1, but a Python list is 0-indexed, so the price of a length-c piece
is price[c - 1]. Off-by-one here is the most common bug in this problem.
Practice
For price = [1, 5, 8, 9] and n = 4, you have already computed dp[1] = 1, dp[2] = 5, dp[3] = 8. What is dp[4]?
1. What does dp[L] represent?
2. Why must dp be filled from short lengths to long ones?
3. For the inner loop, what is the candidate revenue for a first cut of length c?
4. What is the time complexity of the bottom-up solution?