Stage 3: Dynamic Programming·DP on Trees & Intervals
Hard#15478 min readDynamic ProgrammingIntervalsLeetCode

Minimum Cost to Cut a Stick

Interval DP over sorted cut positions

Interval DP. Sort cuts, then dp[i][j] = min cost to process segment.

Published Apr 17, 2026

Problem

Given a stick of length n (positions 0..n) and an array cuts of positions where cuts must happen, make all cuts. Each cut costs the current length of the piece being cut. Return the minimum total cost over all cutting orders.

Input
n = 7, cuts = [1, 3, 4, 5]
Output
16
Explanation
One optimal order: cut at 3 first (cost 7), then 5 (cost 4), then 1 (cost 3), then 4 (cost 2). Total 16.
Input
n = 9, cuts = [5, 6, 1, 4, 2]
Output
22
Input
n = 10, cuts = [1]
Output
10

Intuition

Cut order matters: cutting the stick into a short piece first leaves a long piece to keep paying for. You can't naively greedy — example: [1, 3, 4, 5] with greedy-smallest-first gives 7 + 6 + 4 + 3 = 20, worse than optimal 16. Greedy-largest-first sometimes works, sometimes doesn't. The problem wants the provable minimum.

The right shape is #312's reframe: ask which cut happens last in each segment. When cut k is the last one in segment (L, R), at that moment the piece's length is exactly R - L (no cuts remain inside), and the two sides (L, k) and (k, R) are fully independent.

Sort cuts and pad with [0, n] so cuts[0] = 0 and cuts[m - 1] = n. Let dp[i][j] = minimum cost to make all cuts strictly between indices i and j (in the padded array). Then:

dp[i][j] = min_{k in (i, j)} dp[i][k] + dp[k][j] + (cuts[j] - cuts[i])

Base: dp[i][j] = 0 when j - i < 2 (no cuts inside this interval). Answer: dp[0][m - 1].

The (cuts[j] - cuts[i]) term is exactly the length of the piece at the moment the last cut is made — identical to Burst Balloons' v[l] * v[k] * v[r] except we sum instead of multiply.

Approach

Greedy (incorrect)

TimeO(m log m)
SpaceO(1)

"Cut largest segment first" sometimes wins, sometimes loses. [1, 3, 4, 5]: greedy picks 3 (middle) first (cost 7, segments [0,3] and [3,7]), then 1 in [0,3] (cost 3), then 5 in [3,7] (cost 4), then 4 in [3,5] (cost 2) = 16. Got lucky. But [5, 6, 1, 4, 2] with "cut middle first" picks 4 from [0,9] (cost 9), then on [0,4] picks 2 (cost 4), then on [0,2] picks 1 (cost 2), then on [4,9] picks 6 (cost 5), then on [4,6] picks 5 (cost 2) = 22 — that happens to match. But in general, local optima don't combine; DP is required.

No code — the point is that greedy is a trap, not a solution.

Interval DP (last cut)

TimeO(m³)
SpaceO(m²)
Recommended

m = cuts.len() + 2 after padding. Sort first. Fill by increasing interval length. Length 2 means j = i + 2, so there's exactly one k = i + 1 strictly inside.

impl Solution {
    pub fn min_cost(n: i32, mut cuts: Vec<i32>) -> i32 {
        cuts.push(0);
        cuts.push(n);
        cuts.sort();
        let m = cuts.len();
        let mut dp = vec![vec![0i32; m]; m];
        for len in 2..m {
            for i in 0..(m - len) {
                let j = i + len;
                let mut best = i32::MAX;
                for k in (i + 1)..j {
                    let cost = dp[i][k] + dp[k][j] + (cuts[j] - cuts[i]);
                    if cost < best {
                        best= cost;
                    }
                }
                dp[i][j]= best;
            }
        }
        dp[0][m - 1]
    }
}
Rust · runnable

Trace

Padded sorted cuts for n = 7, cuts = [1, 3, 4, 5]: [0, 1, 3, 4, 5, 7] (indices 0–5).

len(i, j)best kdp[i][j]
2(0, 2)k=13
2(1, 3)k=23
2(2, 4)k=32
2(3, 5)k=44
3(0, 3)k=27
3(1, 4)k=27
3(2, 5)k=38
4(0, 4)k=212
4(1, 5)k=213
5(0, 5)k=216

Return dp[0][5] = 16. ✓

Edge cases

  • Empty cuts — return 0. Padded array is [0, n], length 2, no interior. The loop body never runs.
  • Single cut — only one position to consider at length 2. The best k is trivially that cut; cost is n.
  • Cuts that are already sorted — harmless; the sort is idempotent.
  • cuts contains 0 or n — LC constraints disallow this. If it could happen, sort_dedup first so the padding doesn't introduce a duplicate that breaks the dp[i][j] = 0 base case.
💡
Tip

Interval DPs of this shape — "a fixed list of events, choose the order, pay a position-dependent cost" — always respond to the same reframe: which event happens last? #312 (bursting balloons), #1547 (making cuts), and matrix-chain multiplication (#1000000… not on this roadmap but everywhere in textbooks) all reduce to the same skeleton. Recognize the family and the code practically writes itself.

Takeaway

Cut Stick is the "last X" interval-DP template applied to a scalar cost instead of Burst Balloons' multiplicative cost. Learn the pair: dp[i][j] = min/max over k of dp[i][k] + dp[k][j] + f(i, k, j), where f depends on the problem. The space of variations is large, but the template doesn't change.

If you internalize this, Stage-4's chain-reaction problems (intervals with overlapping constraints, weighted choices, compound actions) are just variations on these three recurrences you've now written: match-or-skip (LCS/Edit Distance), take-or-skip (knapsack), and last-X (interval DP).

More in DP on Trees & Intervals