Facebook Pixel

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 line i (where i ranges from 0 to m-2)
  • verticalCut[j] represents the cost to make a cut along vertical line j (where j ranges from 0 to n-2)

When you make a cut:

  1. You can choose any piece of cake that is not yet a 1 x 1 square
  2. You can cut it either horizontally or vertically
  3. The cut divides that piece into two separate pieces
  4. 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.

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

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 cost
  • i = j = 0: Pointers for traversing horizontalCut and verticalCut arrays
  • h = 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 through v 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 through h 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 Evaluator

Example 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 vs horizontalCut[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 vs horizontalCut[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 vs horizontalCut[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 vs horizontalCut[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) takes O((m-1) × log(m-1)) time since horizontalCut has m-1 elements
  • verticalCut.sort(reverse=True) takes O((n-1) × log(n-1)) time since verticalCut has n-1 elements
  • The while loop runs for exactly (m-1) + (n-1) iterations, performing O(1) operations in each iteration, contributing O(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 sorting horizontalCut and O(log(n-1)) space for sorting verticalCut in the worst case for the recursion stack
  • The variables ans, i, j, h, and v use O(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 through k+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 have k+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 requires m-1 horizontal cuts and n-1 vertical cuts
  • The arrays horizontalCut and verticalCut have lengths m-1 and n-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:

  1. If vertical cuts are exhausted (v_index == n - 1), must make horizontal cut
  2. If both cuts are available, choose the more expensive one
  3. Otherwise, make the vertical cut (including when horizontal cuts are exhausted)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More