Facebook Pixel

1547. Minimum Cost to Cut a Stick

Problem Description

You have a wooden stick of length n units that is labeled from 0 to n. You need to make cuts at specific positions given in an array cuts, where cuts[i] represents a position where you should make a cut.

The key points of the problem are:

  1. You can perform the cuts in any order - you're not required to cut in the order given in the cuts array.

  2. Cost of each cut - The cost of making a cut is equal to the length of the stick being cut at that moment. For example, if you're cutting a stick of length 10, the cost is 10.

  3. Stick splitting - After each cut, the stick splits into two smaller pieces. The sum of their lengths equals the original stick's length.

  4. Total cost - You need to find the minimum total cost to make all the required cuts.

For example, if you have a stick of length 7 and need to make cuts at positions [1, 3, 4, 5]:

  • If you first cut at position 3, it costs 7 (the current stick length), creating pieces [0,3] and [3,7]
  • Then cutting at position 1 costs 3 (length of [0,3]), creating [0,1] and [1,3]
  • Cutting at position 5 costs 4 (length of [3,7]), creating [3,5] and [5,7]
  • Finally cutting at position 4 costs 2 (length of [3,5]), creating [3,4] and [4,5]
  • Total cost: 7 + 3 + 4 + 2 = 16

The challenge is to find the optimal order of cuts that minimizes the total cost.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we look at this problem, we notice that the order of cuts matters significantly for the total cost. Each cut's cost depends on the current length of the stick being cut, which changes based on previous cuts. This suggests we need to explore different cutting orders.

The key insight is that once we decide to make a cut at position k in a stick segment from position i to position j, we're essentially dividing the problem into two independent subproblems: cutting the left segment [i, k] and cutting the right segment [k, j]. The total cost for this segment would be:

  • The cost of making the cut at k (which is the length j - i)
  • Plus the minimum cost to handle all cuts in the left segment
  • Plus the minimum cost to handle all cuts in the right segment

This recursive structure immediately suggests a dynamic programming approach. We can think of it as: for any segment of the stick, what's the minimum cost to make all required cuts within that segment?

To make this work cleanly, we add 0 and n to our cuts array as boundaries. This way, every segment has clear start and end points. After sorting the cuts array, we can think of the problem as finding the minimum cost to cut between any two positions in our enhanced cuts array.

The problem becomes an interval DP problem where we build up solutions for smaller intervals and use them to solve larger intervals. For an interval [i, j], we try every possible first cut k between i and j, and choose the one that gives us the minimum total cost. The formula becomes:

f[i][j] = min(f[i][k] + f[k][j] + cuts[j] - cuts[i]) for all valid k

This approach ensures we consider all possible cutting orders and find the optimal one.

Learn more about Dynamic Programming and Sorting patterns.

Solution Approach

The solution uses interval dynamic programming to find the minimum cost. Here's how the implementation works:

Step 1: Prepare the cuts array

cuts.extend([0, n])
cuts.sort()

We add the boundaries 0 and n to the cuts array, then sort it. This gives us all the positions where cuts can potentially divide the stick, including the endpoints. If we have m elements in the sorted array, we have m-1 intervals between consecutive cut positions.

Step 2: Initialize the DP table

f = [[0] * m for _ in range(m)]

We create a 2D DP table f[i][j] where f[i][j] represents the minimum cost to make all cuts between positions cuts[i] and cuts[j]. Initially, all values are 0, which correctly represents the base case where intervals with no cuts inside (intervals of length 2) have zero cost.

Step 3: Fill the DP table by interval length

for l in range(2, m):
    for i in range(m - l):
        j = i + l

We iterate through intervals by their length l (number of cut positions in the interval). Starting from intervals of length 2 (which need no cuts) up to length m-1 (the entire stick). For each length, we iterate through all possible starting positions i, and calculate the ending position as j = i + l.

Step 4: Find minimum cost for each interval

f[i][j] = inf
for k in range(i + 1, j):
    f[i][j] = min(f[i][j], f[i][k] + f[k][j] + cuts[j] - cuts[i])

For each interval [i, j], we try every possible position k to make the first cut (where i < k < j). The cost consists of:

  • cuts[j] - cuts[i]: The cost of making the cut at position k (length of current stick segment)
  • f[i][k]: The minimum cost to handle all cuts in the left sub-interval
  • f[k][j]: The minimum cost to handle all cuts in the right sub-interval

We choose the k that gives the minimum total cost.

Step 5: Return the result

return f[0][-1]

The answer is f[0][m-1], which represents the minimum cost to make all cuts in the interval from position cuts[0] = 0 to position cuts[m-1] = n, i.e., the entire stick.

The time complexity is O(m³) where m is the number of cuts plus 2, and the space complexity is O(m²) for the DP table.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example: We have a stick of length n = 7 and need to make cuts at positions cuts = [1, 3, 4, 5].

Step 1: Prepare the cuts array

  • Add boundaries: cuts = [1, 3, 4, 5, 0, 7]
  • Sort: cuts = [0, 1, 3, 4, 5, 7]
  • We now have m = 6 positions

Step 2: Initialize DP table Create a 6×6 table f[i][j] initialized with zeros. Each f[i][j] will store the minimum cost to make all cuts between positions cuts[i] and cuts[j].

Step 3: Fill DP table by interval length

Length 2 intervals (no cuts needed):

  • f[0][2] (positions 0→3): Try cut at position 1. Cost = f[0][1] + f[1][2] + (3-0) = 0 + 0 + 3 = 3
  • f[1][3] (positions 1→4): Try cut at position 3. Cost = 0 + 0 + (4-1) = 3
  • f[2][4] (positions 3→5): Try cut at position 4. Cost = 0 + 0 + (5-3) = 2
  • f[3][5] (positions 4→7): Try cut at position 5. Cost = 0 + 0 + (7-4) = 3

Length 3 intervals:

  • f[0][3] (positions 0→4):
    • Try cut at 1: f[0][1] + f[1][3] + 4 = 0 + 3 + 4 = 7
    • Try cut at 3: f[0][2] + f[2][3] + 4 = 3 + 0 + 4 = 7
    • Minimum: 7
  • f[1][4] (positions 1→5):
    • Try cut at 3: f[1][2] + f[2][4] + 4 = 0 + 2 + 4 = 6
    • Try cut at 4: f[1][3] + f[3][4] + 4 = 3 + 0 + 4 = 7
    • Minimum: 6
  • f[2][5] (positions 3→7):
    • Try cut at 4: f[2][3] + f[3][5] + 4 = 0 + 3 + 4 = 7
    • Try cut at 5: f[2][4] + f[4][5] + 4 = 2 + 0 + 4 = 6
    • Minimum: 6

Length 4 intervals:

  • f[0][4] (positions 0→5):
    • Try cut at 1: f[0][1] + f[1][4] + 5 = 0 + 6 + 5 = 11
    • Try cut at 3: f[0][2] + f[2][4] + 5 = 3 + 2 + 5 = 10
    • Try cut at 4: f[0][3] + f[3][4] + 5 = 7 + 0 + 5 = 12
    • Minimum: 10
  • f[1][5] (positions 1→7):
    • Try cut at 3: f[1][2] + f[2][5] + 6 = 0 + 6 + 6 = 12
    • Try cut at 4: f[1][3] + f[3][5] + 6 = 3 + 3 + 6 = 12
    • Try cut at 5: f[1][4] + f[4][5] + 6 = 6 + 0 + 6 = 12
    • Minimum: 12

Length 5 interval (entire stick):

  • f[0][5] (positions 0→7):
    • Try cut at 1: f[0][1] + f[1][5] + 7 = 0 + 12 + 7 = 19
    • Try cut at 3: f[0][2] + f[2][5] + 7 = 3 + 6 + 7 = 16
    • Try cut at 4: f[0][3] + f[3][5] + 7 = 7 + 3 + 7 = 17
    • Try cut at 5: f[0][4] + f[4][5] + 7 = 10 + 0 + 7 = 17
    • Minimum: 16

Step 4: Return result The answer is f[0][5] = 16, which represents the minimum cost to make all cuts in the stick.

This corresponds to the optimal cutting order: first cut at position 3 (cost 7), then cut at 1 (cost 3), then at 5 (cost 4), and finally at 4 (cost 2), for a total of 16.

Solution Implementation

1class Solution:
2    def minCost(self, n: int, cuts: List[int]) -> int:
3        # Add boundaries (0 and n) to the cuts array
4        cuts.extend([0, n])
5        # Sort all cut positions for ordered processing
6        cuts.sort()
7      
8        # Total number of positions including boundaries
9        m = len(cuts)
10      
11        # dp[i][j] represents minimum cost to make all cuts between position i and j
12        dp = [[0] * m for _ in range(m)]
13      
14        # Iterate over all possible lengths of segments
15        # length starts from 2 (at least 3 positions: start, one cut, end)
16        for length in range(2, m):
17            # Try all possible starting positions for current length
18            for i in range(m - length):
19                # Calculate ending position based on starting position and length
20                j = i + length
21              
22                # Initialize with infinity to find minimum
23                dp[i][j] = float('inf')
24              
25                # Try all possible positions to make the first cut
26                # k represents the position of the cut between i and j
27                for k in range(i + 1, j):
28                    # Cost = left segment + right segment + current cut cost
29                    # Current cut cost is the length of stick being cut (cuts[j] - cuts[i])
30                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i])
31      
32        # Return minimum cost to cut the entire stick (from position 0 to last position)
33        return dp[0][-1]
34
1class Solution {
2    public int minCost(int n, int[] cuts) {
3        // Create a list to store all cut positions including boundaries
4        List<Integer> cutPositions = new ArrayList<>();
5      
6        // Add all original cut positions
7        for (int cut : cuts) {
8            cutPositions.add(cut);
9        }
10      
11        // Add the start (0) and end (n) boundaries of the stick
12        cutPositions.add(0);
13        cutPositions.add(n);
14      
15        // Sort all positions to process them in order
16        Collections.sort(cutPositions);
17      
18        // Total number of positions (cuts + 2 boundaries)
19        int totalPositions = cutPositions.size();
20      
21        // dp[i][j] represents the minimum cost to cut the stick between position i and j
22        int[][] dp = new int[totalPositions][totalPositions];
23      
24        // Iterate through all possible stick lengths
25        // length represents the distance between two positions in the array
26        for (int length = 2; length < totalPositions; length++) {
27            // Try all possible starting positions for current length
28            for (int left = 0; left + length < totalPositions; left++) {
29                // Calculate the right boundary for current segment
30                int right = left + length;
31              
32                // Initialize with a large value (representing infinity)
33                dp[left][right] = 1 << 30;
34              
35                // Try all possible first cuts between left and right
36                for (int cutPoint = left + 1; cutPoint < right; cutPoint++) {
37                    // Calculate cost: cost of left segment + cost of right segment + current cut cost
38                    // Current cut cost is the length of the stick being cut (right position - left position)
39                    int currentCost = dp[left][cutPoint] + dp[cutPoint][right] + 
40                                     cutPositions.get(right) - cutPositions.get(left);
41                  
42                    // Update minimum cost
43                    dp[left][right] = Math.min(dp[left][right], currentCost);
44                }
45            }
46        }
47      
48        // Return the minimum cost to cut the entire stick (from position 0 to last position)
49        return dp[0][totalPositions - 1];
50    }
51}
52
1class Solution {
2public:
3    int minCost(int n, vector<int>& cuts) {
4        // Add boundaries: 0 (start) and n (end) to the cuts array
5        cuts.push_back(0);
6        cuts.push_back(n);
7      
8        // Sort cuts to process them in order
9        sort(cuts.begin(), cuts.end());
10      
11        // Total number of cut positions including boundaries
12        int totalCuts = cuts.size();
13      
14        // dp[i][j] = minimum cost to cut the stick between cuts[i] and cuts[j]
15        int dp[110][110]{};
16      
17        // Iterate through all possible lengths of segments
18        // length starts from 2 (at least one cut between boundaries)
19        for (int length = 2; length < totalCuts; ++length) {
20            // Try all possible starting positions for current length
21            for (int left = 0; left + length < totalCuts; ++left) {
22                // Calculate ending position based on length
23                int right = left + length;
24              
25                // Initialize with a large value (infinity)
26                dp[left][right] = 1 << 30;
27              
28                // Try all possible cut positions between left and right
29                for (int cutPos = left + 1; cutPos < right; ++cutPos) {
30                    // Cost = left segment cost + right segment cost + current cut cost
31                    // Current cut cost = length of segment being cut (cuts[right] - cuts[left])
32                    dp[left][right] = min(dp[left][right], 
33                                          dp[left][cutPos] + dp[cutPos][right] + cuts[right] - cuts[left]);
34                }
35            }
36        }
37      
38        // Return minimum cost to cut the entire stick (from position 0 to totalCuts-1)
39        return dp[0][totalCuts - 1];
40    }
41};
42
1/**
2 * Calculates the minimum cost to cut a stick at specified positions
3 * @param n - The length of the stick
4 * @param cuts - Array of positions where cuts need to be made
5 * @returns The minimum total cost of making all cuts
6 */
7function minCost(n: number, cuts: number[]): number {
8    // Add boundaries (0 and n) to the cuts array
9    cuts.push(0, n);
10  
11    // Sort cuts in ascending order to process them sequentially
12    cuts.sort((a, b) => a - b);
13  
14    const cutsCount: number = cuts.length;
15  
16    // dp[i][j] represents the minimum cost to make all cuts between cuts[i] and cuts[j]
17    const dp: number[][] = Array.from(
18        { length: cutsCount }, 
19        () => Array(cutsCount).fill(0)
20    );
21  
22    // Iterate through all possible lengths of intervals
23    for (let intervalLength = 2; intervalLength < cutsCount; intervalLength++) {
24        // Try all possible starting positions for current interval length
25        for (let left = 0; left < cutsCount - intervalLength; left++) {
26            const right: number = left + intervalLength;
27          
28            // Initialize with infinity to find minimum
29            dp[left][right] = Infinity;
30          
31            // Try all possible cutting points between left and right
32            for (let cutPoint = left + 1; cutPoint < right; cutPoint++) {
33                // Cost = left sub-problem + right sub-problem + cost of current cut
34                // Cost of current cut = length of current segment (cuts[right] - cuts[left])
35                dp[left][right] = Math.min(
36                    dp[left][right], 
37                    dp[left][cutPoint] + dp[cutPoint][right] + cuts[right] - cuts[left]
38                );
39            }
40        }
41    }
42  
43    // Return the minimum cost to make all cuts from position 0 to position n
44    return dp[0][cutsCount - 1];
45}
46

Time and Space Complexity

The time complexity of this code is O(m^3), where m is the length of the modified cuts array after adding the boundaries 0 and n.

The analysis breaks down as follows:

  • After extending cuts with [0, n], the array has m = len(cuts) elements
  • The outer loop iterates through lengths from 2 to m-1, giving O(m) iterations
  • The middle loop iterates through starting positions, with up to m - l iterations for each length, averaging O(m) iterations
  • The inner loop iterates through all possible split points k between i+1 and j-1, which is O(m) in the worst case
  • Overall: O(m) × O(m) × O(m) = O(m^3)

The space complexity is O(m^2) due to:

  • The 2D DP table f with dimensions m × m, requiring O(m^2) space
  • The sorted cuts array uses O(m) additional space, but this is dominated by the DP table

Therefore, the time complexity is O(m^3) and the space complexity is O(m^2), where m is the length of the modified cuts array.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Add Boundaries to the Cuts Array

One of the most common mistakes is forgetting to add the stick boundaries (0 and n) to the cuts array. Without these boundaries, the DP solution cannot properly calculate the cost of cuts at the edges of the stick.

Incorrect approach:

def minCost(self, n: int, cuts: List[int]) -> int:
    cuts.sort()  # Only sorting original cuts
    m = len(cuts)
    dp = [[0] * m for _ in range(m)]
    # ... rest of the logic

Why it fails: Without boundaries, you can't calculate the actual length of stick segments. For example, if cuts = [3, 5] and n = 7, you need to know that the first segment is from 0 to 3 (length 3), not just starting at position 3.

Solution: Always add the boundaries before processing:

cuts.extend([0, n])  # Add boundaries
cuts.sort()

2. Incorrect Loop Boundaries When Iterating Through Intervals

Another pitfall is using wrong loop boundaries, especially for the innermost loop that tries different cut positions.

Incorrect approach:

for k in range(i, j):  # Wrong: includes i
    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i])

Why it fails: Including k = i would mean making a cut at the starting position of the interval, which doesn't make sense. Similarly, including k = j would mean cutting at the end position.

Solution: The cut position k must be strictly between i and j:

for k in range(i + 1, j):  # Correct: k is strictly between i and j

3. Not Handling the Base Case Properly

Some implementations try to explicitly set base cases for intervals with no cuts, which can lead to errors if not done correctly.

Incorrect approach:

# Trying to explicitly set base cases
for i in range(m - 1):
    dp[i][i + 1] = 0  # This is redundant and can cause confusion

Why it's problematic: The initialization dp = [[0] * m for _ in range(m)] already handles the base case. Intervals of length 1 (like dp[i][i+1]) naturally have no cuts between them, so their cost is 0.

Solution: Trust the zero initialization for base cases. The DP table is initialized with zeros, which correctly represents that intervals with no interior cuts have zero cost.

4. Confusing Interval Length with Number of Cuts

A subtle pitfall is misunderstanding what the length variable represents in the outer loop.

Incorrect understanding:

for num_cuts in range(1, m):  # Thinking this is the number of cuts
    for i in range(m - num_cuts):
        j = i + num_cuts

Why it's confusing: The variable actually represents the distance between indices in the cuts array, not the number of cuts. An interval from cuts[i] to cuts[i+2] has length 2 in the array but contains only 1 cut position (at cuts[i+1]).

Solution: Think of it as interval length in the sorted cuts array:

for length in range(2, m):  # length is the span in the cuts array
    # An interval of length 2 means cuts[i] to cuts[i+2], with one cut at cuts[i+1]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the minimum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More