The cheapest way to cut a stick
A stick of length n must be cut at every position in cuts (distinct, strictly between 0 and n).
Making a cut costs the length of the piece being cut at that moment. Return the smallest total cost over all orders of
cutting.
Examples
| Input | Output | Why |
|---|---|---|
n = 7, cuts = [1,3,4,5] | 16 | Cut 3, then 5, then 1, then 4: 7 + 4 + 3 + 2. |
n = 9, cuts = [5,6,1,4,2] | 22 | The order of cuts matters a lot. |
n = 5, cuts = [2] | 5 | One cut of the whole stick. |
Constraints
- 2 ≤ n ≤ 10⁶
- 1 ≤ cuts.length ≤ min(n − 1, 100)
- All cuts distinct, 0 < cuts[i] < n
Hints
Hint 1
Sort the cuts and add the two ends, 0 and n, as boundaries.
Hint 2
cost(i, j) = cheapest way to make every cut strictly between boundaries i and j.
Hint 3
Choose the first cut k in (i, j): it costs the piece length p[j] − p[i], and splits the work into (i, k) and (k, j).
Where people lose marks
- A table indexed by stick length, which is far too large.
- Forgetting to sort the cuts.
- Greedy middle-first cutting, which is not always optimal.
Dry run on 7,[1,3,4,5]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 4boundaries001132435475
Pieces containing 1 cut: [0, 3] first cut at 1: 3; [1, 4] first cut at 3: 3; [3, 5] first cut at 4: 2; [4, 7] first cut at 5: 3.
-
Step 2 / 4boundaries001132435475
Pieces containing 2 cuts: [0, 4] first cut at 1: 7; [1, 5] first cut at 3: 6; [3, 7] first cut at 5: 6.
-
Step 3 / 4boundaries001132435475
Pieces containing 3 cuts: [0, 5] first cut at 3: 10; [1, 7] first cut at 3: 12.
-
Step 4 / 4boundaries001132435475
Pieces containing 4 cuts: [0, 7] first cut at 3: 16. The answer is 16.
How to think about it
Sort the cut positions and add 0 and n, giving boundaries p. The piece between boundaries i and j must be
cut at every position strictly inside it. Whichever cut k is made first on that piece costs its whole length,
p[j] − p[i], and leaves two independent pieces. So cost(i, j) = p[j] − p[i] + min over k of cost(i, k) +
cost(k, j), zero when there is nothing inside, filled by increasing gap. The table is over cut positions (at most 102),
not over lengths, which is why n can be a million.
Complexity. Time O(m³) for m cuts · Space O(m²)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def min_cut_cost(n, cuts):
p = [0] + sorted(cuts) + [n] # boundaries
m = len(p)
cost = [[0] * m for _ in range(m)] # cost[i][j]: every cut strictly between p[i] and p[j]
for gap in range(2, m):
for i in range(m - gap):
j = i + gap
cost[i][j] = p[j] - p[i] + min(cost[i][k] + cost[k][j] for k in range(i + 1, j)) # k cut first
return cost[0][m - 1]#include <algorithm>
#include <climits>
#include <vector>
long long solve(int n, std::vector<int> cuts) {
std::sort(cuts.begin(), cuts.end());
std::vector<long long> p{0};
for (int c : cuts) p.push_back(c);
p.push_back(n);
const int m = p.size();
std::vector<std::vector<long long>> cost(m, std::vector<long long>(m, 0));
for (int gap = 2; gap < m; ++gap)
for (int i = 0; i + gap < m; ++i) {
int j = i + gap;
long long best = LLONG_MAX;
for (int k = i + 1; k < j; ++k) best = std::min(best, cost[i][k] + cost[k][j]); // k cut first
cost[i][j] = best + p[j] - p[i];
}
return cost[0][m - 1];
}class Solution {
public long minCutCost(int n, int[] cuts) {
int[] sorted = cuts.clone();
java.util.Arrays.sort(sorted);
int m = sorted.length + 2;
long[] p = new long[m];
p[m - 1] = n;
for (int i = 0; i < sorted.length; i++) p[i + 1] = sorted[i];
long[][] cost = new long[m][m];
for (int gap = 2; gap < m; gap++)
for (int i = 0; i + gap < m; i++) {
int j = i + gap;
long best = Long.MAX_VALUE;
for (int k = i + 1; k < j; k++) best = Math.min(best, cost[i][k] + cost[k][j]); // k cut first
cost[i][j] = best + p[j] - p[i];
}
return cost[0][m - 1];
}
}function minCutCost(n, cuts) {
const p = [0, ...[...cuts].sort((a, b) => a - b), n], m = p.length;
const cost = Array.from({ length: m }, () => new Array(m).fill(0));
for (let gap = 2; gap < m; gap++)
for (let i = 0; i + gap < m; i++) {
const j = i + gap;
let best = Infinity;
for (let k = i + 1; k < j; k++) best = Math.min(best, cost[i][k] + cost[k][j]); // k cut first
cost[i][j] = best + p[j] - p[i];
}
return cost[0][m - 1];
}fn solve(n: i64, cuts: &[i64]) -> i64 {
let mut p = vec![0i64];
let mut sorted = cuts.to_vec();
sorted.sort();
p.extend(sorted);
p.push(n);
let m = p.len();
let mut cost = vec![vec![0i64; m]; m];
for gap in 2..m {
for i in 0..m - gap {
let j = i + gap;
let best = (i + 1..j).map(|k| cost[i][k] + cost[k][j]).min().unwrap(); // k cut first
cost[i][j] = best + p[j] - p[i];
}
}
cost[0][m - 1]
}import Data.Array
import Data.List (sort)
solve :: Int -> [Int] -> Int
solve n cuts = cost ! (0, m - 1)
where
ps = [0] ++ sort cuts ++ [n]
m = length ps
p = listArray (0, m - 1) ps
-- cost ! (i, j): making every cut strictly between boundaries i and j
cost = array ((0, 0), (m - 1, m - 1)) [((i, j), go i j) | i <- [0 .. m - 1], j <- [0 .. m - 1]] :: Array (Int, Int) Int
go i j
| j - i < 2 = 0
| otherwise = p ! j - p ! i + minimum [cost ! (i, k) + cost ! (k, j) | k <- [i + 1 .. j - 1]]
Variants to try
- Return the order of cuts.
- Merging piles of stones with merge cost equal to their total (the same recurrence in reverse).
- Optimal binary search trees.
Next problems
Burst balloons for the most coins
Bursting a balloon earns the product of it and its current neighbours. Choosing which balloon goes last in each range makes the two sides independent.
Open the problem → Medium20 minCan the first player win?
Players alternately take a number from either end of a row. With both playing perfectly, does the first player finish with at least as much?
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem →Write a solution and run it against the real test table.
Keep going
Burst balloons for the most coins
Bursting a balloon earns the product of it and its current neighbours. Choosing which balloon goes last in each range makes the two sides independent.
Open the problem → Medium20 minCan the first player win?
Players alternately take a number from either end of a row. With both playing perfectly, does the first player finish with at least as much?
Open the problem → Easy10 minThe cheapest way up a staircase
Each step has a cost; climb one or two steps at a time; start on step 0 or 1. Return the cheapest way past the top.
Open the problem → Dynamic programmingThe pattern behind it
Write the recursion, notice the repeats, fill a table in an order that makes each entry final.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.