GeeksforGeeks Medium

Rod Cutting Problem

Rod Cutting is a GeeksforGeeks practice problem (Medium), not a LeetCode one. Searches for a rod cutting problem LeetCode version usually land on 1547, Minimum Cost to Cut a Stick, which asks something different (see the comparison below).

  • You are given an array price, where price[i - 1] is what a piece of length i sells for.
  • The rod has length n, equal to the length of price.
  • You may cut the rod into any number of whole-number pieces, or not cut it at all, and sell every piece.
  • The same piece length may be sold as many times as you like.

Return the maximum total value you can get. n goes up to 1,000, so trying every cut plan is out; an O(n²) table is expected.

Constraints
  • 1 <= n <= 10³
  • 1 <= price[i] <= 10⁵
  • Pieces must be integer lengths; unlimited cuts of each length
dpknapsack
Open on GeeksforGeeks ↗
02

Intuition

The rod cutting problem looks like it needs every possible cut plan, and there are 2ⁿ⁻¹ of them. The trick is to stop thinking about all the cuts at once and look only at the first piece.

Any plan sells some first piece for its price, and what is left is a shorter rod. The best way to cut that remainder is the same question on a smaller rod, so the best total for each length is the best first piece plus the best answer already known for the rest. Because pieces of one length can be sold as often as you like, this is the unbounded knapsack pattern: rod length is the capacity, piece lengths are the items, prices are the values.

How to spot this pattern

A total to split into parts, each part size with its own value, unlimited use of each size, and a best total to find. That is unbounded knapsack. Coin change (fewest coins), coin change II (count ways) and integer break use the same one-dimensional table over sizes 0..n.

03

Approach

Try it first

Before reading on, write dp[4] for price = [1, 5, 8, 9] by hand, listing all four first-piece choices. Then decide in which order the table must be filled.

1

Define the state

dp[L] = the maximum value obtainable from a rod of length L. The table has n + 1 cells, dp[0] to dp[n], and the answer is dp[n].

2

Set the base case

dp[0] = 0. A rod of length 0 sells for nothing. It is also what the "sell the whole rod uncut" choice reads: price[L - 1] + dp[0].

3

Write the recurrence

For each length L from 1 to n, try every first piece i from 1 to L:

  • dp[L] = max(price[i - 1] + dp[L - i])

The remainder L - i is always smaller than L, so filling lengths in increasing order means every value it reads is ready.

4

Recover the cuts, if asked

Store cut[L], the first piece that gave the maximum. Then from n, take cut[n], move to n - cut[n], and repeat until 0. The pieces listed are an optimal plan.

04

Rod Cutting Problem solution in Python | C++ | Java

▶1class Solution:
▶2 def cutRod(self, price: List[int]) -> int:
▶3 n = len(price)
▶4 dp = [0] * (n + 1)
▶5 for length in range(1, n + 1):
▶6 best = 0
▶7 for first in range(1, length + 1):
▶8 best = max(best, price[first - 1] + dp[length - first])
▶9 dp[length] = best
▶10 return dp[n]
lengthprice11528394105176177208dp0012345678rod8rod of length 8, uncut
n8rod length
dp[0]0base case
State. dp[L] is the most money a rod of length L can earn, cut any way. Every cut plan starts with some first piece, sold as-is, and the rest of the rod is a smaller copy of the same problem. dp[0] = 0 anchors the chain: an empty rod earns nothing.
lengthprice11528394105176177208dp00112345678its pricebest rest11piecerod11len 1=1dp[1]dp[1]: sold whole
length1one choice for the first piece
best first1no cut at all
dp[1]1
A rod of length 1 has one choice: sell it whole for 1. Each bar below the table is one first-piece choice, stacked as its price plus the best for what is left. Selling it uncut for 1 is the tallest bar, so dp[1] = 1.
lengthprice11528394105176177208dp001152345678its pricebest rest2152piecerod25len 2=5dp[2]dp[2]: sold whole
length22 choices for the first piece
best first2no cut at all
dp[2]5
Every first piece for a rod of length 2 is one bar: piece 1 gives 1 + 1 = 2; piece 2 gives 5 + 0 = 5. Selling it uncut for 5 is the tallest bar, so dp[2] = 5.
lengthprice11528394105176177208dp0011528345678its pricebest rest616283piecerod38len 3=8dp[3]dp[3]: sold whole
length33 choices for the first piece
best first3no cut at all
dp[3]8
Every first piece for a rod of length 3 is one bar: piece 1 gives 1 + 5 = 6; piece 2 gives 5 + 1 = 6; piece 3 gives 8 + 0 = 8. Selling it uncut for 8 is the tallest bar, so dp[3] = 8.
lengthprice11528394105176177208dp001152831045678its pricebest rest911029394piecerod225len 2+5dp[2]=10dp[4]dp[4]: piece 2 + best rest 2
length44 choices for the first piece
best first2rest 2 already solved
dp[4]10
Every first piece for a rod of length 4 is one bar: piece 1 gives 1 + 8 = 9; piece 2 gives 5 + 5 = 10; piece 3 gives 8 + 1 = 9; piece 4 gives 9 + 0 = 9. The tallest is a piece of 2 (5) plus the best for the remaining 2 (5, read from the table, not recomputed), so dp[4] = 10.
lengthprice11528394105176177208dp00115283104135678its pricebest rest111132133104105piecerod235len 2+8dp[3]=13dp[5]dp[5]: piece 2 + best rest 3
length55 choices for the first piece
best first2rest 3 already solved
dp[5]13
Every first piece for a rod of length 5 is one bar. The tallest is a piece of 2 (5) plus the best for the remaining 3 (8, read from the table, not recomputed), so dp[5] = 13. 2 bars tie; the loop keeps the first, the smallest piece.
lengthprice11528394105176177208dp0011528310413517678its pricebest rest141152163144115176piecerod617len 6=17dp[6]dp[6]: sold whole
length66 choices for the first piece
best first6no cut at all
dp[6]17
Every first piece for a rod of length 6 is one bar. Selling it uncut for 17 is the tallest bar, so dp[6] = 17.
lengthprice11528394105176177208dp001152831041351761878its pricebest rest181182183174155186177piecerod161len 1+17dp[6]=18dp[7]dp[7]: piece 1 + best rest 6
length77 choices for the first piece
best first1rest 6 already solved
dp[7]18
Every first piece for a rod of length 7 is one bar. The tallest is a piece of 1 (1) plus the best for the remaining 6 (17, read from the table, not recomputed), so dp[7] = 18. 4 bars tie; the loop keeps the first, the smallest piece.
lengthprice11528394105176177208dp00115283104135176187228its pricebest rest191222213194185226187208piecerod265len 2+17dp[6]=22dp[8]dp[8]: piece 2 + best rest 6
length88 choices for the first piece
best first2rest 6 already solved
dp[8]22
Every first piece for a rod of length 8 is one bar. The tallest is a piece of 2 (5) plus the best for the remaining 6 (17, read from the table, not recomputed), so dp[8] = 22. 2 bars tie; the loop keeps the first, the smallest piece.
lengthprice11528394105176177208dp00115283104135176187228rod265len 2+17dp[6]=22dp[8]piece 2 taken · 6 left
remaining8look up the choice made for it
piece2sells for 5
Recover the cuts. When dp[8] was filled, its best first piece was 2. Take that piece; the remaining 6 is still worth dp[6] = 17, so look up the choice stored for length 6 next.
lengthprice11528394105176177208dp00115283104135176187228rod265len 2+17len 6=22dp[8]piece 6 taken · rod used up
remaining6look up the choice made for it
piece6sells for 17
Recover the cuts. When dp[6] was filled, its best first piece was 6. That piece uses up the rod, so the plan is complete.
lengthprice11528394105176177208dp00115283104135176187228rod265len 2+17len 6=22answerbest total → return 22
pieces2 + 6lengths, sum 8
answer225 + 17
Answer 22. Pieces of lengths 2, 6 sell for 22 in total. Each dp cell was computed once from smaller ones, so the table cost O(n²) instead of trying all 2ⁿ⁻¹ cut plans.
05

Memoized recursion

The top-down rod cutting problem Python solution, also in C++ and Java: best(L) returns 0 for an empty rod, and otherwise the best price[i - 1] + best(L - i) over every first piece i. The cache stores each length's answer the first time, so later calls return it at once.

▶1import sys
▶2from functools import cache
▶3 
▶4 
▶5class Solution:
▶6 def cutRod(self, price: List[int]) -> int:
▶7 sys.setrecursionlimit(_000)
▶8 
▶9 @cache
▶10 def best(length: int) -> int:
▶11 if length == 0:
▶12 return 0
▶13 return max(price[i - 1] + best(length - i) for i in range(1, length + 1))
▶14 
▶15 return best(len(price))
06

Common pitfalls

Recursing without memoisation

✗ Wrong
def best(L):
    return max(price[i - 1] + best(L - i) for i in range(1, L + 1))
✓ Right
for L in range(1, n + 1):
    dp[L] = max(price[i - 1] + dp[L - i] for i in range(1, L + 1))

The plain recursion solves best(L - i) again from every caller, which is 2ⁿ⁻¹ calls. The table solves each length exactly once.

Off-by-one between length and index

✗ Wrong
best = max(best, price[first] + dp[length - first])
✓ Right
best = max(best, price[first - 1] + dp[length - first])

price is 0-indexed but lengths start at 1: a piece of length 1 sells for price[0]. Reading price[first] shifts every price by one and reads past the end for first = n.

Forgetting the uncut rod

✗ Wrong
for first in range(1, length):
✓ Right
for first in range(1, length + 1):

first = length means no cut at all. For price = [1, 10, 12] the uncut rod (12) beats every split, and skipping it returns 11.

07

Complexity

Time
O(n²)
Space
O(n)
For length L the rod cutting algorithm tries L first pieces, and 1 + 2 + … + n is about n²/2. The table is one row of n + 1 values.
08

Rod cutting and the problems it is confused with

Three different questions share the rod picture. The table idea is shared; the rule per cell is not.

ProblemWhat is chosenCell rule
Rod cutting (this page)first piece length i, reusabledp[L] = max(price[i-1] + dp[L-i])
Rod cutting problem with cost per cutsame, but each cut costs cmax(price[L-1], max(price[i-1] + dp[L-i] - c))
Unbounded knapsackitem with weight w, value vdp[C] = max(v + dp[C-w])
Minimum Cost to Cut a Stick (LeetCode 1547)which fixed cut to make lastinterval DP over cut positions, O(m³)
09

Rod Cutting Problem FAQ

Why does the rod cutting problem have optimal substructure?

If an optimal plan sells a first piece of length i, the rest of that plan must be an optimal plan for length n - i. If it were not, swapping in a better plan for the rest would raise the total, contradicting optimality.

Why not cut greedily by the best price per unit length?

Because the pieces have to add up to exactly n. The best-rate length may not divide n, and the leftover can be forced into a poor piece. With price = [1, 5, 8, 9] and n = 4, length 3 has the best rate (8/3), but 3 + 1 earns 9 while 2 + 2 earns 10. The table compares every first piece, so it never gets stuck this way.