Minimum Cost for Cutting Cake I
MedPrompt
There is an m x n cake that needs to be cut into 1 x 1 pieces.
You are given integers m, n, and two arrays:
horizontalCutof sizem - 1, wherehorizontalCut[i]represents the cost to cut along the horizontal linei.verticalCutof sizen - 1, whereverticalCut[j]represents the cost to cut along the vertical linej.
In one operation, you can choose any piece of cake that is not yet a 1 x 1 square and perform one of the following cuts:
- Cut along a horizontal line
iat a cost ofhorizontalCut[i]. - Cut along a vertical line
jat a cost ofverticalCut[j].
After the cut, the piece of cake is divided into two distinct pieces.
The cost of a cut depends only on the initial cost of the line and does not change.
Return the minimum total cost to cut the entire cake into 1 x 1 pieces.
Example 1:
Input: m = 3, n = 2, horizontalCut = [1,3], verticalCut = [5]
Output: 13
Explanation:

- Perform a cut on the vertical line 0 with cost 5, current total cost is 5.
- Perform a cut on the horizontal line 0 on
3 x 1subgrid with cost 1. - Perform a cut on the horizontal line 0 on
3 x 1subgrid with cost 1. - Perform a cut on the horizontal line 1 on
2 x 1subgrid with cost 3. - Perform a cut on the horizontal line 1 on
2 x 1subgrid with cost 3.
The total cost is 5 + 1 + 1 + 3 + 3 = 13.
Example 2:
Input: m = 2, n = 2, horizontalCut = [7], verticalCut = [4]
Output: 15
Explanation:
- Perform a cut on the horizontal line 0 with cost 7.
- Perform a cut on the vertical line 0 on
1 x 2subgrid with cost 4. - Perform a cut on the vertical line 0 on
1 x 2subgrid with cost 4.
The total cost is 7 + 4 + 4 = 15.
Constraints:
1 <= m, n <= 20horizontalCut.length == m - 1verticalCut.length == n - 11 <= horizontalCut[i], verticalCut[i] <= 103
Approaches
2 approaches with complexity analysis and trade-offs.
This approach attempts to solve the problem by exploring all possible sequences of cuts. A recursive function is defined to calculate the minimum cost from a given state, where the state is defined by which cuts have already been performed. To optimize the plain recursion, memoization (a top-down dynamic programming technique) is used to store and reuse the results of subproblems that have already been solved.
Algorithm
- Define a recursive function, say
solve(hMask, vMask), wherehMaskandvMaskare bitmasks representing the horizontal and vertical cuts that have already been made. - The base case for the recursion is when all cuts are made (i.e., all bits in
hMaskandvMaskare set). In this case, the cost is 0. - In the recursive step, calculate the current number of horizontal pieces (
hPieces = popcount(hMask) + 1) and vertical pieces (vPieces = popcount(vMask) + 1). - Iterate through all possible next cuts (both horizontal and vertical) that haven't been made yet.
- For each available horizontal cut
i, calculate the cost ashorizontalCut[i] * vPiecesand recursively callsolvewith the updatedhMask. - For each available vertical cut
j, calculate the cost asverticalCut[j] * hPiecesand recursively callsolvewith the updatedvMask. - The function returns the minimum cost found among all possible next cuts.
- Use a 2D array for memoization,
memo[hMask][vMask], to store the results of subproblems and avoid redundant calculations.
Walkthrough
The state of our recursive function can be defined by the set of cuts that have already been made. We can use two integer bitmasks, hMask and vMask, to keep track of the horizontal and vertical cuts performed. solve(hMask, vMask) would then compute the minimum additional cost to cut the cake completely, given the cuts represented by the masks have been made.
The number of horizontal pieces is Integer.bitCount(hMask) + 1, and the number of vertical pieces is Integer.bitCount(vMask) + 1. When we decide to make a new horizontal cut i, its cost will be horizontalCut[i] multiplied by the current number of vertical pieces. Similarly, a new vertical cut j costs verticalCut[j] multiplied by the current number of horizontal pieces.
The recursion explores every possible next cut, adds its cost to the result of the subsequent recursive call, and finds the minimum among all choices. However, with m, n <= 20, the number of states (2^(m-1) * 2^(n-1)) is prohibitively large, leading to a Time Limit Exceeded (TLE) error.
// This is a conceptual implementation and will result in Time Limit Exceeded.class Solution { long[][] memo; int m, n; int[] horizontalCut, verticalCut; public long minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) { this.m = m; this.n = n; this.horizontalCut = horizontalCut; this.verticalCut = verticalCut; // The state space 2^(m-1) * 2^(n-1) is too large for the given constraints. // For example, if m=20, n=20, we'd need a memo table of size 2^19 * 2^19. // This code is for demonstration of the concept. // memo = new long[1 << (m - 1)][1 << (n - 1)]; // for (long[] row : memo) Arrays.fill(row, -1); return solve(0, 0); } private long solve(int hMask, int vMask) { if (hMask == (1 << (m - 1)) - 1 && vMask == (1 << (n - 1)) - 1) { return 0; } // if (memo[hMask][vMask] != -1) return memo[hMask][vMask]; long minCost = Long.MAX_VALUE; int hPieces = Integer.bitCount(hMask) + 1; int vPieces = Integer.bitCount(vMask) + 1; // Try making an unmade horizontal cut for (int i = 0; i < m - 1; i++) { if ((hMask & (1 << i)) == 0) { long currentCost = (long) horizontalCut[i] * vPieces; minCost = Math.min(minCost, currentCost + solve(hMask | (1 << i), vMask)); } } // Try making an unmade vertical cut for (int j = 0; j < n - 1; j++) { if ((vMask & (1 << j)) == 0) { long currentCost = (long) verticalCut[j] * hPieces; minCost = Math.min(minCost, currentCost + solve(hMask, vMask | (1 << j))); } } // return memo[hMask][vMask] = minCost; return minCost; }}Complexity
Time
O((m+n) * 2^(m+n)). There are `2^(m+n-2)` states, and for each state, we iterate through up to `m+n-2` possible cuts.
Space
O(2^(m+n)). The memoization table would have `2^(m-1) * 2^(n-1)` states. This is infeasible for the given constraints.
Trade-offs
Pros
It is a systematic way to explore the entire search space, guaranteeing an optimal solution if it runs to completion.
The logic is a direct translation of the problem definition into a recursive structure.
Cons
The time complexity is exponential, making it too slow for the given constraints.
The space complexity is also exponential due to the memoization table, which is not feasible for the given constraints.
A greedy approach provides an efficient and optimal solution. The main idea is that to minimize the total cost, we should prioritize making cuts with higher costs. The cost of a cut is its base value multiplied by the number of pieces it divides. By making expensive cuts early, when the cake is in fewer pieces, we minimize their contribution to the total cost. This suggests processing all cuts (both horizontal and vertical) in descending order of their costs.
Algorithm
- The key insight is that it is always optimal to perform the cut with the highest cost first.
- Sort both
horizontalCutandverticalCutarrays in descending order. - Initialize
hPieces = 1,vPieces = 1(representing one initial cake piece), andtotalCost = 0. - Use two pointers,
ifor the sorted horizontal cuts andjfor the sorted vertical cuts. - In a loop, compare the costs of the current pointed-to horizontal and vertical cuts (
horizontalCut[i]andverticalCut[j]). - If the horizontal cut has a higher cost, add
horizontalCut[i] * vPiecestototalCost, incrementhPieces, and move the horizontal pointeri. - Otherwise, the vertical cut has a higher or equal cost. Add
verticalCut[j] * hPiecestototalCost, incrementvPieces, and move the vertical pointerj. - Continue until all cuts have been processed. The final
totalCostis the minimum possible cost.
Walkthrough
This greedy strategy can be proven correct using an exchange argument. Assume there is an optimal sequence of cuts where a cut c1 is performed before a cut c2, but cost(c1) < cost(c2). By swapping their order, we can show that the total cost either decreases or stays the same. This implies that an optimal sequence must have cuts sorted in descending order of cost.
To implement this, we can sort both the horizontal and vertical cut costs in descending order. Then, we can use a two-pointer technique to iterate through them, always picking the cut with the higher cost from the two arrays. We maintain a count of the number of horizontal and vertical pieces. When we make a horizontal cut, its cost is multiplied by the current number of vertical pieces. When we make a vertical cut, its cost is multiplied by the current number of horizontal pieces.
import java.util.Arrays;import java.util.Collections; class Solution { public long minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) { // To sort in descending order, we can use Integer arrays. Integer[] hCut = Arrays.stream(horizontalCut).boxed().toArray(Integer[]::new); Integer[] vCut = Arrays.stream(verticalCut).boxed().toArray(Integer[]::new); Arrays.sort(hCut, Collections.reverseOrder()); Arrays.sort(vCut, Collections.reverseOrder()); long totalCost = 0; int hPieces = 1; int vPieces = 1; int i = 0; // pointer for horizontal cuts int j = 0; // pointer for vertical cuts while (i < hCut.length || j < vCut.length) { long hCost = (i < hCut.length) ? hCut[i] : -1; long vCost = (j < vCut.length) ? vCut[j] : -1; // Greedily pick the more expensive cut. if (hCost > vCost) { // Make a horizontal cut. totalCost += hCost * vPieces; hPieces++; i++; } else { // Make a vertical cut. totalCost += vCost * hPieces; vPieces++; j++; } } return totalCost; }}Complexity
Time
O(m log m + n log n). The dominant operation is sorting the two arrays of cut costs. The subsequent two-pointer traversal takes O(m + n) time.
Space
O(m + n). This is for storing the `Integer` arrays for sorting. If we sort the primitive arrays and use a custom comparator or sort in ascending and iterate backwards, we can reduce auxiliary space, but `O(m+n)` is a reasonable upper bound for this implementation.
Trade-offs
Pros
Highly efficient with a polynomial time complexity.
Simple to implement once the greedy strategy is understood.
Guaranteed to find the optimal solution.
Cons
The greedy choice is not immediately obvious and its correctness requires a proof (e.g., using an exchange argument).
Solutions
Solution
class Solution {public int minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) { Arrays.sort(horizontalCut); Arrays.sort(verticalCut); int ans = 0; int i = m - 2, j = n - 2; int h = 1, v = 1; while (i >= 0 || j >= 0) { if (j < 0 || (i >= 0 && horizontalCut[i] > verticalCut[j])) { ans += horizontalCut[i--] * v; ++h; } else { ans += verticalCut[j--] * h; ++v; } } return ans; }}class Solution {public: int minimumCost(int m, int n, vector<int> &horizontalCut, vector<int> &verticalCut) { sort(horizontalCut.rbegin(), horizontalCut.rend()); sort(verticalCut.rbegin(), verticalCut.rend()); int ans = 0; int i = 0, j = 0; int h = 1, v = 1; while (i < m - 1 || j < n - 1) { if (j == n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) { ans += horizontalCut[i++] * v; h++; } else { ans += verticalCut[j++] * h; v++; } } return ans; }};class Solution: def minimumCost(self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]) -> int: horizontalCut . sort(reverse=True) verticalCut . sort(reverse=True) ans = i = j = 0 h = v = 1 while i < m - 1 or j < n - 1: if j == n - 1 or (i < m - 1 and horizontalCut[i] > verticalCut[j]): ans += horizontalCut[i] * v h, i = h + 1, i + 1 else: ans += verticalCut[j] * h v, j = v + 1, j + 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.