Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1547. Minimum Cost to Cut a Stick - Leetcode Solution

Problem Description

You are given a stick of length n and an array cuts where each element represents a position along the stick (from 0 to n) where you must make a cut. You can make the cuts in any order. After a cut, the stick is split into two smaller sticks and you continue making cuts on those pieces as needed.

The cost of making a cut is equal to the length of the stick being cut at that moment. Your task is to determine the minimum total cost to perform all the cuts.

  • Each cut position in cuts is unique and between 1 and n-1.
  • All cuts must be made; you cannot reuse or skip any cut.
  • Return the minimum total cost to cut the stick into all the required pieces.

Thought Process

At first, it may seem like you could just cut the stick in any order and sum up the costs. However, the cost depends on the current length of the stick being cut, and making cuts in different orders can lead to different total costs.

This is a classic example of a problem where greedy choices do not always yield the optimal result. For instance, always making the smallest or largest cut first can lead to suboptimal solutions. Therefore, we need to consider all possible orders of making the cuts and choose the one with the minimum total cost.

This leads us to consider a recursive or dynamic programming approach, where we break the problem into subproblems: what's the minimum cost to cut a stick between two positions, considering only the cuts within that segment?

Solution Approach

To solve this problem efficiently, we use dynamic programming. Here's how we break it down:

  1. Augment the cuts: Add 0 and n to the cuts array (representing the start and end of the stick), and sort the array. This way, we can always refer to the left and right boundaries of any stick segment.
  2. Define subproblems: Let dp[i][j] represent the minimum cost to cut the stick between cuts[i] and cuts[j].
  3. Recurrence relation: For each segment (i, j), try making every possible cut k between i and j (i.e., i < k < j). The cost is the length of the segment (cuts[j] - cuts[i]) plus the minimum cost to cut the left and right subsegments:
    • dp[i][j] = min(dp[i][k] + dp[k][j] + cuts[j] - cuts[i]) for all k in (i, j)
  4. Base case: If there are no cuts between i and j (i.e., j - i == 1), the cost is 0.
  5. Build up the solution: Fill the DP table from smaller segments to larger ones.

This approach ensures we consider all possible ways to order the cuts, but we avoid recomputation by storing results in the DP table.

Example Walkthrough

Let's walk through an example with n = 7 and cuts = [1, 3, 4, 5].

  1. Augment and sort cuts: cuts = [0, 1, 3, 4, 5, 7]
  2. We want to find dp[0][5], the minimum cost to cut from position 0 to 7.
  3. For each subsegment, we compute the minimum cost by trying each possible cut within that segment.
  4. For example, to cut from 0 to 7, possible first cuts are at positions 1, 3, 4, and 5. For each, we recursively compute the cost for the left and right segments, and add the cost of the current cut (which is 7 - 0 = 7).
  5. This process continues recursively, filling in the DP table for smaller segments first, then using those results for larger segments.
  6. The final answer, dp[0][5], is 16 (as per the sample in Leetcode).

Code Implementation

class Solution:
    def minCost(self, n: int, cuts: List[int]) -> int:
        cuts = [0] + sorted(cuts) + [n]
        m = len(cuts)
        dp = [[0] * m for _ in range(m)]
        for l in range(2, m):
            for i in range(m - l):
                j = i + l
                dp[i][j] = min(
                    dp[i][k] + dp[k][j] for k in range(i + 1, j)
                ) + cuts[j] - cuts[i]
        return dp[0][m - 1]
      
class Solution {
public:
    int minCost(int n, vector<int>& cuts) {
        cuts.push_back(0);
        cuts.push_back(n);
        sort(cuts.begin(), cuts.end());
        int m = cuts.size();
        vector<vector<int>> dp(m, vector<int>(m, 0));
        for (int len = 2; len < m; ++len) {
            for (int i = 0; i + len < m; ++i) {
                int j = i + len;
                dp[i][j] = INT_MAX;
                for (int k = i + 1; k < j; ++k) {
                    dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]);
                }
            }
        }
        return dp[0][m - 1];
    }
};
      
class Solution {
    public int minCost(int n, int[] cuts) {
        int m = cuts.length + 2;
        int[] newCuts = new int[m];
        newCuts[0] = 0;
        newCuts[m - 1] = n;
        for (int i = 0; i < cuts.length; i++) {
            newCuts[i + 1] = cuts[i];
        }
        Arrays.sort(newCuts);
        int[][] dp = new int[m][m];
        for (int len = 2; len < m; len++) {
            for (int i = 0; i + len < m; i++) {
                int j = i + len;
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i + 1; k < j; k++) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j] + newCuts[j] - newCuts[i]);
                }
            }
        }
        return dp[0][m - 1];
    }
}
      
var minCost = function(n, cuts) {
    cuts.push(0);
    cuts.push(n);
    cuts.sort((a, b) => a - b);
    const m = cuts.length;
    const dp = Array.from({length: m}, () => Array(m).fill(0));
    for (let len = 2; len < m; ++len) {
        for (let i = 0; i + len < m; ++i) {
            let j = i + len;
            dp[i][j] = Infinity;
            for (let k = i + 1; k < j; ++k) {
                dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i]);
            }
        }
    }
    return dp[0][m - 1];
};
      

Time and Space Complexity

Brute-force approach: If we tried all possible orders of cuts recursively, the number of possible ways grows exponentially (factorial in the number of cuts), which is infeasible for large inputs.

Dynamic programming approach:

  • There are O(m2) subproblems, where m is the number of cuts plus 2 (for 0 and n).
  • For each subproblem, we try up to O(m) possible cuts.
  • Thus, total time complexity is O(m3).
  • Space complexity is O(m2) for the DP table.

This is efficient enough for the constraints in the problem.

Summary

The Minimum Cost to Cut a Stick problem is a classic example of interval dynamic programming. The key insight is to break the problem into subproblems defined by stick segments and solve each using previously computed solutions. By augmenting the cuts array and carefully filling a DP table, we efficiently find the order of cuts that yields the minimum total cost. This approach avoids brute-force enumeration and leverages overlapping subproblems for optimal performance.