3219. Minimum Cost for Cutting Cake II
Problem Description
There is a cake with dimensions m x n
that needs to be divided into 1 x 1
pieces. You're provided with the integers m
, n
, and two arrays:
horizontalCut
of lengthm - 1
, wherehorizontalCut[i]
represents the cost to cut along the horizontal linei
.verticalCut
of lengthn - 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
i
at a cost ofhorizontalCut[i]
. - Cut along a vertical line
j
at a cost ofverticalCut[j]
.
Each cut divides a piece of cake into two smaller ones.
The cost of a cut is determined solely by the initial cost of the line and remains constant. The task is to find the minimum total cost to divide the cake into 1 x 1
pieces.
Intuition
To minimize the total cost of cutting the cake into 1 x 1
pieces, we need a strategy that prioritizes making cuts with higher costs as earlier as possible. This leads us to a greedy approach.
-
Sorting by Cost: Start by sorting
horizontalCut
andverticalCut
in descending order. This ensures we evaluate the more expensive cuts first. -
Two-Pointer Technique: Use two pointers, one for each array (
i
forhorizontalCut
andj
forverticalCut
).- Begin by considering the highest-cost available cut between the two arrays.
- Always apply the cut that has the higher cost between the current horizontal and vertical cuts.
-
Cost Calculation: When a horizontal cut is made, multiply its cost by the current number of vertical sections (
v
). When a vertical cut is made, multiply its cost by the current number of horizontal sections (h
). -
Iterative Update: Each time a horizontal cut is made, increase the number of horizontal sections (
h
) by 1. Similarly, increase the vertical sections (v
) by 1 for each vertical cut. -
Completion: Continue this process until both pointers have traversed their respective arrays, ensuring all necessary cuts have been considered.
This strategy leverages the fact that more expensive cuts should be executed when they impact the maximum number of resulting pieces, thus effectively minimizing total expense.
Solution Approach
To solve the problem of minimizing the cost to cut the cake into 1 x 1
pieces, we deploy a greedy strategy combined with a two-pointer technique. Here's a breakdown of the solution approach:
-
- Start by sorting
horizontalCut
andverticalCut
in descending order. This is essential as it allows us to handle the highest cost cuts first, which is crucial in a greedy strategy seeking to minimize overall costs.
- Start by sorting
-
Initialization:
- Initialize two pointers
i
andj
to zero. These pointers will track our position in thehorizontalCut
andverticalCut
arrays, respectively. - Set
h
(count of horizontal sections) andv
(count of vertical sections) to 1, as initially the whole cake is a single piece.
- Initialize two pointers
-
Iterative Cutting:
- Use a loop to continue making cuts until either
i >= m - 1
orj >= n - 1
. This ensures both dimensions are fully processed. - In each iteration, decide between making the next horizontal or vertical cut. If the value at
horizontalCut[i]
is greater thanverticalCut[j]
, perform the horizontal cut:- Add
horizontalCut[i] * v
to the total cost (ans
). - Increment
h
by 1 since an additional horizontal section is created. - Move to the next horizontal cut by incrementing
i
.
- Add
- Else, perform the vertical cut:
- Add
verticalCut[j] * h
to the total cost (ans
). - Increment
v
by 1 as an additional vertical section is affected. - Move to the next vertical cut by incrementing
j
.
- Add
- Use a loop to continue making cuts until either
-
Final Calculation:
- Once both pointers reach the end of their respective arrays, the loop ends. The accumulated cost in
ans
is the minimum total cost required to cut the entire cake into1 x 1
pieces.
- Once both pointers reach the end of their respective arrays, the loop ends. The accumulated cost in
This approach ensures that the most impactful decisions are made first by considering the descending order of costs, achieving an optimal solution efficiently. The use of two pointers and sorting simplifies the process, making the implementation both intuitive and effective.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach. Consider a cake with dimensions 3 x 3
, with the following cutting costs:
horizontalCut = [2, 3]
verticalCut = [1, 4]
Step-by-step breakdown:
-
Sorting:
- Sort both arrays in descending order:
horizontalCut = [3, 2]
,verticalCut = [4, 1]
.
- Sort both arrays in descending order:
-
Initialization:
- Initialize two pointers:
i = 0
andj = 0
. - Set initial sections:
h = 1
(horizontal),v = 1
(vertical). - Total cost,
ans = 0
.
- Initialize two pointers:
-
Iterative Cutting:
-
Iteration 1: Compare
horizontalCut[i]
(3) withverticalCut[j]
(4). Since 4 > 3, perform a vertical cut.- Add
verticalCut[0] * h = 4 * 1 = 4
toans
. (Total cost:ans = 4
) - Increment
v
to2
(2 vertical sections). - Increment
j
to1
.
- Add
-
Iteration 2: Now, compare
horizontalCut[i]
(3) withverticalCut[j]
(1). Since 3 > 1, perform a horizontal cut.- Add
horizontalCut[0] * v = 3 * 2 = 6
toans
. (Total cost:ans = 10
) - Increment
h
to2
(2 horizontal sections). - Increment
i
to1
.
- Add
-
Iteration 3: Compare
horizontalCut[i]
(2) withverticalCut[j]
(1). Since 2 > 1, perform a horizontal cut.- Add
horizontalCut[1] * v = 2 * 2 = 4
toans
. (Total cost:ans = 14
) - Increment
h
to3
(3 horizontal sections). - Increment
i
to2
.
- Add
-
-
Completion:
- Now,
i = 2
, which equalsm - 1
. All horizontal lines are processed. - Only a vertical cut remains with
j = 1
. - Add
verticalCut[1] * h = 1 * 3 = 3
toans
. (Final total cost:ans = 17
)
- Now,
The minimum cost to cut the entire cake into 1 x 1
pieces is 17
.
Solution Implementation
1from typing import List
2
3class Solution:
4 def minimumCost(
5 self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]
6 ) -> int:
7 # Sort horizontal and vertical cuts in descending order
8 horizontalCut.sort(reverse=True)
9 verticalCut.sort(reverse=True)
10
11 # Initialize variables to track total cost and index for cuts
12 total_cost = 0
13 i = 0
14 j = 0
15 horizontal_segments = 1
16 vertical_segments = 1
17
18 # Process cuts until all are exhausted
19 while i < m - 1 or j < n - 1:
20 if j == n - 1 or (i < m - 1 and horizontalCut[i] > verticalCut[j]):
21 # Use horizontal cut if all vertical cuts are used or
22 # current horizontal cut is greater than current vertical cut
23 total_cost += horizontalCut[i] * vertical_segments
24 horizontal_segments += 1
25 i += 1
26 else:
27 # Use vertical cut otherwise
28 total_cost += verticalCut[j] * horizontal_segments
29 vertical_segments += 1
30 j += 1
31
32 return total_cost
33
1import java.util.Arrays;
2
3class Solution {
4 public long minimumCost(int m, int n, int[] horizontalCuts, int[] verticalCuts) {
5 // Sort the horizontal and vertical cuts in ascending order
6 Arrays.sort(horizontalCuts);
7 Arrays.sort(verticalCuts);
8
9 long totalCost = 0; // Initialize the total cost
10 int i = m - 2; // Initialize the index for horizontalCuts, starting just before last element
11 int j = n - 2; // Initialize the index for verticalCuts, starting just before last element
12 int horizontalMultiplier = 1; // Keep track of the segments created by current horizontal cuts
13 int verticalMultiplier = 1; // Keep track of the segments created by current vertical cuts
14
15 // Process cuts from largest to smallest
16 while (i >= 0 || j >= 0) {
17 // If there are no more vertical cuts to process or the current horizontal cut is larger
18 // than the current vertical cut, process a horizontal cut
19 if (j < 0 || (i >= 0 && horizontalCuts[i] > verticalCuts[j])) {
20 totalCost += (long) horizontalCuts[i--] * verticalMultiplier;
21 horizontalMultiplier++;
22 } else {
23 // Otherwise, process a vertical cut
24 totalCost += (long) verticalCuts[j--] * horizontalMultiplier;
25 verticalMultiplier++;
26 }
27 }
28
29 return totalCost; // Return the calculated minimum cost
30 }
31}
32
1class Solution {
2public:
3 long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {
4 // Sort horizontal and vertical cuts in descending order
5 sort(horizontalCut.rbegin(), horizontalCut.rend());
6 sort(verticalCut.rbegin(), verticalCut.rend());
7
8 long long totalCost = 0; // Variable to store the total minimum cost
9 int i = 0, j = 0; // Pointers to iterate through horizontal and vertical cuts
10 int horizontalPieces = 1; // Initial number of horizontal pieces
11 int verticalPieces = 1; // Initial number of vertical pieces
12
13 // Traverse the cuts while there are still cuts left in either direction
14 while (i < m - 1 || j < n - 1) {
15 if (j == n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {
16 // Add cost if the next horizontal cut has greater cost or there are no more vertical cuts
17 totalCost += 1LL * horizontalCut[i++] * verticalPieces;
18 horizontalPieces++; // Increase the number of horizontal pieces
19 } else {
20 // Add cost if the next vertical cut has greater cost
21 totalCost += 1LL * verticalCut[j++] * horizontalPieces;
22 verticalPieces++; // Increase the number of vertical pieces
23 }
24 }
25 return totalCost;
26 }
27};
28
1// Function to calculate the minimum cost of cutting a piece of material into smaller sections
2function minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {
3 // Sort cuts in descending order to prioritize larger cuts first
4 horizontalCut.sort((a, b) => b - a);
5 verticalCut.sort((a, b) => b - a);
6
7 let totalCost = 0; // Initialize total cost
8 let [i, j] = [0, 0]; // Initialize indices for horizontal and vertical cuts
9 let [horizontalPieces, verticalPieces] = [1, 1]; // Initialize piece counts
10
11 // Continue until all cuts are processed
12 while (i < m - 1 || j < n - 1) {
13 // Check if vertical cuts are exhausted or horizontal cut is larger
14 if (j === n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {
15 // Add the cost of horizontal cut times the number of vertical pieces
16 totalCost += horizontalCut[i] * verticalPieces;
17 horizontalPieces++; // Increase the number of horizontal pieces
18 i++; // Move to the next horizontal cut
19 } else {
20 // Add the cost of vertical cut times the number of horizontal pieces
21 totalCost += verticalCut[j] * horizontalPieces;
22 verticalPieces++; // Increase the number of vertical pieces
23 j++; // Move to the next vertical cut
24 }
25 }
26
27 return totalCost; // Return the computed total cost
28}
29
Time and Space Complexity
The time complexity is O(m * log m + n * log n)
, which arises because we sort both horizontalCut
and verticalCut
. Sorting each array takes O(m * log m)
and O(n * log n)
time, respectively.
The space complexity is O(log m + log n)
. This space is primarily used by the sorting process due to the recursive nature of the sorting algorithm, typically requiring additional space related to the recursion depth, which is O(log m)
for the horizontal cuts and O(log n)
for the vertical cuts.
Learn more about how to find time and space complexity quickly.
Which of these pictures shows the visit order of a depth-first search?
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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!