3218. Minimum Cost for Cutting Cake I
Problem Description
You have an m x n
rectangular cake that needs to be completely cut into 1 x 1
pieces.
The cake has m - 1
possible horizontal cut lines and n - 1
possible vertical cut lines. Each cut line has an associated cost:
horizontalCut[i]
represents the cost to make a cut along horizontal linei
(wherei
ranges from 0 tom-2
)verticalCut[j]
represents the cost to make a cut along vertical linej
(wherej
ranges from 0 ton-2
)
When you make a cut:
- You can choose any piece of cake that is not yet a
1 x 1
square - You can cut it either horizontally or vertically
- The cut divides that piece into two separate pieces
- The cost of making a cut along a specific line is always the same, regardless of how many times that line has been used in other pieces
The key insight is that when you cut along a line, you're actually cutting through all the pieces that the line passes through. For example:
- If you make a horizontal cut when the cake has already been divided into 3 vertical strips, you're cutting through all 3 strips, so the total cost is
horizontalCut[i] * 3
- Similarly, if you make a vertical cut when the cake has 2 horizontal layers, the cost is
verticalCut[j] * 2
Your goal is to find the minimum total cost to completely cut the cake into 1 x 1
pieces.
The solution uses a greedy approach: always make the most expensive cuts first (when they affect fewer pieces), which minimizes the total cost since expensive cuts will be multiplied by smaller numbers of pieces.
Intuition
Let's think about what happens when we make cuts. When we cut a horizontal line through the cake, we're actually cutting through all the vertical pieces that exist at that moment. If the cake has been divided into v
vertical strips, then making a horizontal cut costs horizontalCut[i] * v
. Similarly, a vertical cut through h
horizontal layers costs verticalCut[j] * h
.
The key observation is that each cut we make increases the number of pieces for future cuts. If we make a horizontal cut, we increase the number of horizontal layers by 1, which means all future vertical cuts will be more expensive. The same applies in reverse.
This creates a multiplication effect: if we have a cut with cost 10 and another with cost 5, the order matters significantly:
- Cut 10 first, then 5:
10 * 1 + 5 * 2 = 20
- Cut 5 first, then 10:
5 * 1 + 10 * 2 = 25
The more expensive cut should be made earlier when it affects fewer pieces!
This leads us to the greedy strategy: always make the most expensive cut available. By sorting both arrays in descending order and using two pointers, we can always choose the currently most expensive cut between horizontal and vertical options. Each time we make a cut, we update the count of how many pieces the perpendicular cuts will need to go through.
The beauty of this approach is that it guarantees the minimum cost because we're minimizing the multiplication factor for expensive cuts - they're always made when the number of pieces they need to cut through is as small as possible.
Learn more about Greedy, Dynamic Programming and Sorting patterns.
Solution Approach
The implementation follows a greedy algorithm with two pointers to efficiently select the most expensive cuts first.
Step 1: Sort the cut arrays
horizontalCut.sort(reverse=True) verticalCut.sort(reverse=True)
We sort both horizontalCut
and verticalCut
arrays in descending order so that the most expensive cuts appear first.
Step 2: Initialize variables
ans = 0
: Tracks the total costi = j = 0
: Pointers for traversinghorizontalCut
andverticalCut
arraysh = v = 1
: Initially, we have 1 horizontal piece and 1 vertical piece (the whole cake)
Step 3: Process cuts using two pointers
while i < m - 1 or j < n - 1:
We continue until all cuts are made (both arrays are fully traversed).
At each iteration, we decide whether to make a horizontal or vertical cut by comparing:
- If we've exhausted all vertical cuts (
j == n - 1
), we must make a horizontal cut - If we still have both types available and
horizontalCut[i] > verticalCut[j]
, we choose the horizontal cut (higher cost) - Otherwise, we make a vertical cut
Step 4: Apply the selected cut
When making a horizontal cut:
ans += horizontalCut[i] * v h, i = h + 1, i + 1
- The cost is
horizontalCut[i] * v
because this horizontal line cuts throughv
vertical pieces - We increment
h
(number of horizontal layers increases by 1) - We move to the next horizontal cut with
i + 1
When making a vertical cut:
ans += verticalCut[j] * h v, j = v + 1, j + 1
- The cost is
verticalCut[j] * h
because this vertical line cuts throughh
horizontal pieces - We increment
v
(number of vertical strips increases by 1) - We move to the next vertical cut with
j + 1
Time Complexity: O(m log m + n log n)
for sorting, then O(m + n)
for the two-pointer traversal.
Space Complexity: O(1)
if we don't count the sorting space, otherwise O(log m + log n)
for the sorting algorithm.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Example: We have a 3×4 cake (m=3, n=4)
horizontalCut = [1, 3]
(costs for 2 horizontal cuts)verticalCut = [5, 2, 4]
(costs for 3 vertical cuts)
Step 1: Sort arrays in descending order
horizontalCut = [3, 1]
verticalCut = [5, 4, 2]
Step 2: Initialize variables
ans = 0
(total cost)i = 0, j = 0
(pointers)h = 1, v = 1
(initially one whole piece)
Step 3: Process cuts greedily
Iteration 1:
- Compare:
verticalCut[0] = 5
vshorizontalCut[0] = 3
- Choose vertical cut (5 > 3)
- Cost:
5 × 1 = 5
(cutting through 1 horizontal piece) - Update:
ans = 5
,v = 2
,j = 1
- Cake is now in 2 vertical strips
Iteration 2:
- Compare:
verticalCut[1] = 4
vshorizontalCut[0] = 3
- Choose vertical cut (4 > 3)
- Cost:
4 × 1 = 4
(cutting through 1 horizontal piece) - Update:
ans = 9
,v = 3
,j = 2
- Cake is now in 3 vertical strips
Iteration 3:
- Compare:
verticalCut[2] = 2
vshorizontalCut[0] = 3
- Choose horizontal cut (3 > 2)
- Cost:
3 × 3 = 9
(cutting through 3 vertical strips!) - Update:
ans = 18
,h = 2
,i = 1
- Cake is now in 2 horizontal × 3 vertical = 6 pieces
Iteration 4:
- Compare:
verticalCut[2] = 2
vshorizontalCut[1] = 1
- Choose vertical cut (2 > 1)
- Cost:
2 × 2 = 4
(cutting through 2 horizontal pieces) - Update:
ans = 22
,v = 4
,j = 3
- Cake is now in 2 horizontal × 4 vertical = 8 pieces
Iteration 5:
- Only horizontal cuts remain
- Choose horizontal cut
- Cost:
1 × 4 = 4
(cutting through 4 vertical strips) - Update:
ans = 26
,h = 3
,i = 2
- Cake is now in 3 horizontal × 4 vertical = 12 pieces (all 1×1)
Final Answer: 26
Notice how making expensive cuts early (when they affect fewer pieces) minimizes the total cost. If we had made the cheap cuts first, the expensive cuts would have been multiplied by larger numbers, resulting in a higher total cost.
Solution Implementation
1class Solution:
2 def minimumCost(
3 self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]
4 ) -> int:
5 # Sort both cut arrays in descending order to prioritize expensive cuts
6 horizontalCut.sort(reverse=True)
7 verticalCut.sort(reverse=True)
8
9 # Initialize variables
10 total_cost = 0 # Total cost accumulator
11 h_index = 0 # Index for horizontal cuts
12 v_index = 0 # Index for vertical cuts
13 h_pieces = 1 # Number of horizontal pieces (rows)
14 v_pieces = 1 # Number of vertical pieces (columns)
15
16 # Process all cuts (m-1 horizontal cuts and n-1 vertical cuts)
17 while h_index < m - 1 or v_index < n - 1:
18 # Determine which cut to make next based on greedy strategy
19 if v_index == n - 1 or (h_index < m - 1 and horizontalCut[h_index] > verticalCut[v_index]):
20 # Make horizontal cut
21 # Cost is cut price multiplied by number of vertical pieces it crosses
22 total_cost += horizontalCut[h_index] * v_pieces
23 h_pieces += 1 # Increment number of horizontal pieces
24 h_index += 1 # Move to next horizontal cut
25 else:
26 # Make vertical cut
27 # Cost is cut price multiplied by number of horizontal pieces it crosses
28 total_cost += verticalCut[v_index] * h_pieces
29 v_pieces += 1 # Increment number of vertical pieces
30 v_index += 1 # Move to next vertical cut
31
32 return total_cost
33
1class Solution {
2 public int minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {
3 // Sort both cut arrays in ascending order to process from highest cost
4 Arrays.sort(horizontalCut);
5 Arrays.sort(verticalCut);
6
7 // Initialize total cost
8 int totalCost = 0;
9
10 // Pointers to traverse cuts from highest to lowest (arrays are sorted ascending)
11 int horizontalIndex = m - 2; // m-1 horizontal cuts, starting from last index
12 int verticalIndex = n - 2; // n-1 vertical cuts, starting from last index
13
14 // Track number of segments in each direction
15 int horizontalSegments = 1; // Initially 1 horizontal segment
16 int verticalSegments = 1; // Initially 1 vertical segment
17
18 // Process all cuts using greedy approach - always pick the most expensive cut
19 while (horizontalIndex >= 0 || verticalIndex >= 0) {
20 // Determine which cut to make next based on cost comparison
21 if (verticalIndex < 0 ||
22 (horizontalIndex >= 0 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {
23 // Make horizontal cut
24 // Cost = cut cost * number of vertical segments it crosses
25 totalCost += horizontalCut[horizontalIndex] * verticalSegments;
26 horizontalIndex--;
27 horizontalSegments++; // One more horizontal segment after this cut
28 } else {
29 // Make vertical cut
30 // Cost = cut cost * number of horizontal segments it crosses
31 totalCost += verticalCut[verticalIndex] * horizontalSegments;
32 verticalIndex--;
33 verticalSegments++; // One more vertical segment after this cut
34 }
35 }
36
37 return totalCost;
38 }
39}
40
1class Solution {
2public:
3 int minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {
4 // Sort both cut arrays in descending order to prioritize expensive cuts first
5 sort(horizontalCut.rbegin(), horizontalCut.rend());
6 sort(verticalCut.rbegin(), verticalCut.rend());
7
8 int totalCost = 0;
9 int horizontalIndex = 0; // Index for traversing horizontal cuts
10 int verticalIndex = 0; // Index for traversing vertical cuts
11 int horizontalPieces = 1; // Number of horizontal pieces after cuts
12 int verticalPieces = 1; // Number of vertical pieces after cuts
13
14 // Process all cuts using a greedy approach
15 while (horizontalIndex < m - 1 || verticalIndex < n - 1) {
16 // Check if we should make a horizontal cut or vertical cut
17 // Choose horizontal cut if:
18 // 1. All vertical cuts are done, OR
19 // 2. Horizontal cut cost is greater than vertical cut cost
20 if (verticalIndex == n - 1 ||
21 (horizontalIndex < m - 1 &&
22 horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {
23
24 // Make a horizontal cut
25 // Cost = cut price * number of vertical pieces to cut through
26 totalCost += horizontalCut[horizontalIndex] * verticalPieces;
27 horizontalIndex++;
28 horizontalPieces++;
29 } else {
30 // Make a vertical cut
31 // Cost = cut price * number of horizontal pieces to cut through
32 totalCost += verticalCut[verticalIndex] * horizontalPieces;
33 verticalIndex++;
34 verticalPieces++;
35 }
36 }
37
38 return totalCost;
39 }
40};
41
1/**
2 * Calculates the minimum cost to cut a grid into 1x1 pieces
3 * @param m - Number of rows in the grid
4 * @param n - Number of columns in the grid
5 * @param horizontalCut - Array of costs for each horizontal cut
6 * @param verticalCut - Array of costs for each vertical cut
7 * @returns The minimum total cost to cut the entire grid
8 */
9function minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {
10 // Sort both cut arrays in descending order (greedy approach - cut expensive ones first)
11 horizontalCut.sort((a, b) => b - a);
12 verticalCut.sort((a, b) => b - a);
13
14 // Initialize total cost
15 let totalCost: number = 0;
16
17 // Pointers for traversing horizontal and vertical cut arrays
18 let horizontalIndex: number = 0;
19 let verticalIndex: number = 0;
20
21 // Count of horizontal and vertical segments created so far
22 let horizontalSegments: number = 1;
23 let verticalSegments: number = 1;
24
25 // Process all cuts using greedy approach
26 while (horizontalIndex < m - 1 || verticalIndex < n - 1) {
27 // Check if we should make a horizontal cut next
28 if (verticalIndex === n - 1 ||
29 (horizontalIndex < m - 1 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {
30 // Make horizontal cut - cost multiplied by number of vertical segments
31 totalCost += horizontalCut[horizontalIndex] * verticalSegments;
32 horizontalSegments++;
33 horizontalIndex++;
34 } else {
35 // Make vertical cut - cost multiplied by number of horizontal segments
36 totalCost += verticalCut[verticalIndex] * horizontalSegments;
37 verticalSegments++;
38 verticalIndex++;
39 }
40 }
41
42 return totalCost;
43}
44
Time and Space Complexity
Time Complexity: O(m × log m + n × log n)
The time complexity is dominated by the sorting operations:
horizontalCut.sort(reverse=True)
takesO((m-1) × log(m-1))
time sincehorizontalCut
hasm-1
elementsverticalCut.sort(reverse=True)
takesO((n-1) × log(n-1))
time sinceverticalCut
hasn-1
elements- The while loop runs for exactly
(m-1) + (n-1)
iterations, performingO(1)
operations in each iteration, contributingO(m + n)
time
Since O((m-1) × log(m-1)) = O(m × log m)
and O((n-1) × log(n-1)) = O(n × log n)
, and the sorting dominates the linear traversal, the overall time complexity is O(m × log m + n × log n)
.
Space Complexity: O(log m + log n)
The space complexity comes from:
- The sorting algorithm (typically Timsort in Python) uses
O(log(m-1))
space for sortinghorizontalCut
andO(log(n-1))
space for sortingverticalCut
in the worst case for the recursion stack - The variables
ans
,i
,j
,h
, andv
useO(1)
space
Therefore, the overall space complexity is O(log m + log n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Cut Multiplication Logic
A frequent mistake is thinking that each cut has a fixed cost regardless of when it's made. In reality, the cost of a cut depends on how many pieces it passes through at the time of cutting.
Incorrect Understanding:
# WRONG: Simply summing all cut costs
total_cost = sum(horizontalCut) + sum(verticalCut)
Correct Understanding:
- When you make a horizontal cut after
k
vertical cuts have been made, the horizontal cut passes throughk+1
vertical pieces - The actual cost is
horizontalCut[i] * (number_of_vertical_pieces)
2. Incorrect Greedy Choice - Choosing Cheapest Cuts First
Some might intuitively think to minimize cost by making cheaper cuts first, but this is backwards.
Wrong Approach:
# WRONG: Sorting in ascending order (cheapest first) horizontalCut.sort() # Should be reverse=True verticalCut.sort() # Should be reverse=True
Why it's wrong: If you make cheap cuts first, expensive cuts will be made later when there are more pieces, multiplying their cost by larger numbers.
Correct Strategy: Make expensive cuts first when there are fewer pieces to minimize their multiplication factor.
3. Off-by-One Errors in Piece Counting
A subtle but critical error is incorrectly tracking the number of pieces.
Common Mistake:
# WRONG: Starting with 0 pieces h_pieces = 0 # Should start with 1 v_pieces = 0 # Should start with 1
Why it matters:
- Initially, you have 1 whole cake (not 0 pieces)
- After 1 cut, you have 2 pieces (not 1)
- After
k
cuts in one direction, you havek+1
pieces in that direction
4. Index Boundary Confusion
Mixing up the relationship between cake dimensions and number of cuts.
Wrong:
# WRONG: Using m and n as the number of cuts while h_index < m or v_index < n: # Should be m-1 and n-1
Remember:
- An
m x n
cake requiresm-1
horizontal cuts andn-1
vertical cuts - The arrays
horizontalCut
andverticalCut
have lengthsm-1
andn-1
respectively
5. Incorrect Comparison Logic in the Greedy Decision
The condition for choosing which cut to make next must handle edge cases properly.
Incomplete Logic:
# WRONG: Doesn't handle when one array is exhausted if horizontalCut[h_index] > verticalCut[v_index]: # Make horizontal cut
Correct Logic:
if v_index == n - 1 or (h_index < m - 1 and horizontalCut[h_index] > verticalCut[v_index]): # Make horizontal cut
The condition must check:
- If vertical cuts are exhausted (
v_index == n - 1
), must make horizontal cut - If both cuts are available, choose the more expensive one
- Otherwise, make the vertical cut (including when horizontal cuts are exhausted)
A person thinks of a number between 1 and 1000. You may ask any number questions to them, provided that the question can be answered with either "yes" or "no".
What is the minimum number of questions you needed to ask so that you are guaranteed to know the number that the person is thinking?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!