3218. Minimum Cost for Cutting Cake I
Problem Description
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:
horizontalCut
of sizem - 1
, wherehorizontalCut[i]
represents the cost to cut along the horizontal linei
.verticalCut
of 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
i
at a cost ofhorizontalCut[i]
. - Cut along a vertical line
j
at 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.
Intuition
To solve the problem of minimizing the total cost to cut the cake into 1 x 1
pieces, we must understand that the arrangement and order of cuts determine the total cost. Specifically, making cuts with higher costs earlier can be advantageous, as they will apply to more pieces initially.
The key idea is as follows:
- Maximize the Impact of Expensive Cuts: Each cut divides the cake into additional smaller pieces. Thus, if we apply a costly cut early, it affects a larger number of resulting pieces for future cuts. The cost of making a horizontal cut is multiplied by the number of vertical sections (and vice versa). Therefore, making the highest cost cut at each step ensures minimizing total expense when multiplied by its impact.
To implement this strategy:
- Sort the Cuts: First, arrange both
horizontalCut
andverticalCut
in descending order. This allows us to use a greedy approach in selecting the most costly cut at each step. - Use Two Pointers: We maintain two pointers, one for
horizontalCut
and one forverticalCut
. At every step, check which cut has a greater cost and apply it, adjusting the product terms (h
orv
—number of vertical sections when making a horizontal cut, or number of horizontal sections when making a vertical cut). - Increment and Update: Whenever a cut is made, update the respective number of pieces divided, and move to the next cut option by incrementing the pointers.
By following this approach, both horizontal and vertical cuts are handled optimally with respect to their cost and impact on the overall division of the cake. The solution involves sorting, decision-making based on maximum cost impact, and updates that reflect the current number of divisions. This effectively minimizes the total cost required to cut the entire cake into 1 x 1
pieces.
Learn more about Greedy, Dynamic Programming and Sorting patterns.
Solution Approach
The solution to minimize the cost of cutting the entire cake into 1 x 1
pieces involves using a greedy algorithm combined with sorting and two-pointer techniques. Here's how the approach is implemented step-by-step:
-
- Start by sorting both
horizontalCut
andverticalCut
arrays in descending order. This prepares us to consider the most costly cuts first, which is crucial for minimizing the total cost.
horizontalCut.sort(reverse=True) verticalCut.sort(reverse=True)
- Start by sorting both
-
Initialization:
- Initialize variables to keep track of the total cost (
ans
), pointers for the horizontal and vertical cut indices (i
andj
), and the number of horizontal and vertical parts (h
andv
). Initially, the entire cake is one whole piece, henceh = 1
andv = 1
.
ans = i = j = 0 h = v = 1
- Initialize variables to keep track of the total cost (
-
Two Pointers and Greedy Selection:
- Use a while loop to iterate through until all possible cuts are considered. In each iteration:
- Compare the current highest horizontal and vertical cut costs.
- Make the cut with the higher cost to minimize future expenses. Adjust the respective horizontal or vertical part count.
- If a horizontal cut is made, update the equation
ans += horizontalCut[i] * v
. - If a vertical cut is made, update the equation
ans += verticalCut[j] * h
. - Increment the appropriate pointer (
i
for horizontal cuts andj
for vertical cuts) and the respective segment count (h
orv
).
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
- Use a while loop to iterate through until all possible cuts are considered. In each iteration:
-
Return the Total Minimum Cost:
- At the end of the loop,
ans
contains the minimum total cost for cutting the cake into1 x 1
pieces.
return ans
- At the end of the loop,
This approach effectively uses the properties of greedy algorithms by minimizing cost at each step through strategic selection of cuts, ensuring that the most expensive cut impacts the largest current piece size. The use of sorting, two-pointer technique, and greedy selection ensures an optimal solution.
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 illustrate the solution approach with a small example:
Consider a cake of size 3 x 3
(so m = 3
and n = 3
). We are given two arrays representing the costs of cuts:
horizontalCut = [2, 3]
verticalCut = [1, 4]
Our goal is to cut the cake into 1 x 1
pieces while minimizing the total cost.
1. Sorting:
We start by sorting both horizontalCut
and verticalCut
in descending order:
horizontalCut
becomes[3, 2]
verticalCut
becomes[4, 1]
2. Initialization:
We initialize the total cost ans
to 0
, and set the indices for the horizontal and vertical cuts i
and j
to 0
. We also initialize the number of horizontal sections h
and vertical sections v
to 1
.
ans = i = j = 0 h = v = 1
3. Two Pointers and Greedy Selection:
We process the cuts using a while loop, selecting the more costly option at each iteration:
-
Iteration 1:
horizontalCut[0]
is3
,verticalCut[0]
is4
.- Choose the vertical cut since
4 > 3
. - Update:
ans += 4 * 1 = 4
, incrementv
to2
, move to the next vertical cut by settingj = 1
.
-
Iteration 2:
horizontalCut[0]
is3
,verticalCut[1]
is1
.- Choose the horizontal cut since
3 > 1
. - Update:
ans += 3 * 2 = 6
, incrementh
to2
, move to the next horizontal cut by settingi = 1
.
-
Iteration 3:
horizontalCut[1]
is2
,verticalCut[1]
is1
.- Choose the horizontal cut since
2 > 1
. - Update:
ans += 2 * 2 = 4
, incrementh
to3
,i
reaches2
which is the limit for horizontal cuts.
-
Iteration 4:
- Now, all horizontal cuts are done. Focus on the remaining vertical cut.
- Choose the vertical cut
verticalCut[1]
, which is1
. - Update:
ans += 1 * 3 = 3
, incrementv
to3
,j
reaches2
which is the limit for vertical cuts.
Conclusion:
Now, the entire cake is divided into 1 x 1
pieces. The total minimum cost ans
is 17
.
return ans # Final result is 17
By following this example, we can see how each decision at each step impacts the total cost based on the current number of sections affected by the cuts.
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 cut costs in descending order
8 horizontalCut.sort(reverse=True)
9 verticalCut.sort(reverse=True)
10
11 # Initialize variables
12 total_cost = 0 # Total cost of all cuts
13 i = j = 0 # Indices for horizontal and vertical cuts respectively
14 h = v = 1 # Number of horizontal and vertical pieces created
15
16 # Loop until all cuts have been made
17 while i < m - 1 or j < n - 1:
18 # Decide whether to cut horizontally or vertically
19 # If all vertical cuts are done or the next horizontal cut is greater than the next vertical cut
20 if j == n - 1 or (i < m - 1 and horizontalCut[i] > verticalCut[j]):
21 total_cost += horizontalCut[i] * v # Multiply by current vertical pieces
22 h += 1 # Increase horizontal piece count
23 i += 1 # Move to the next horizontal cut
24 else:
25 # Otherwise, perform a vertical cut
26 total_cost += verticalCut[j] * h # Multiply by current horizontal pieces
27 v += 1 # Increase vertical piece count
28 j += 1 # Move to the next vertical cut
29
30 return total_cost
31
1import java.util.Arrays;
2
3class Solution {
4 public int minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {
5 // Sort the arrays of horizontal and vertical cuts
6 Arrays.sort(horizontalCut);
7 Arrays.sort(verticalCut);
8
9 int totalCost = 0;
10 int horizontalIndex = m - 2; // Start from the largest horizontal cut
11 int verticalIndex = n - 2; // Start from the largest vertical cut
12 int horizontalPieces = 1; // Initially one piece horizontally
13 int verticalPieces = 1; // Initially one piece vertically
14
15 // Process the cuts while there are still cuts left to consider
16 while (horizontalIndex >= 0 || verticalIndex >= 0) {
17 // If no vertical cut remains or the current horizontal cut is larger
18 if (verticalIndex < 0 || (horizontalIndex >= 0 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {
19 // Add cost of the horizontal cut multiplied by the number of vertical pieces
20 totalCost += horizontalCut[horizontalIndex--] * verticalPieces;
21 // Increase number of horizontal pieces as the board is cut
22 ++horizontalPieces;
23 } else {
24 // Add cost of the vertical cut multiplied by the number of horizontal pieces
25 totalCost += verticalCut[verticalIndex--] * horizontalPieces;
26 // Increase number of vertical pieces as the board is cut
27 ++verticalPieces;
28 }
29 }
30
31 return totalCost;
32 }
33}
34
1class Solution {
2public:
3 int 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 int totalCost = 0; // Initialize total cost
9 int horizontalIndex = 0, verticalIndex = 0; // Initialize indices to iterate through cuts
10 int horizontalPieces = 1, verticalPieces = 1; // Track the number of pieces horizontally and vertically
11
12 // Loop until all cuts are made
13 while (horizontalIndex < m - 1 || verticalIndex < n - 1) {
14 // Choose the next cut based on the remaining parts and maximize the cost
15 if (verticalIndex == n - 1 || (horizontalIndex < m - 1 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {
16 // Perform a horizontal cut
17 totalCost += horizontalCut[horizontalIndex++] * verticalPieces;
18 horizontalPieces++;
19 } else {
20 // Perform a vertical cut
21 totalCost += verticalCut[verticalIndex++] * horizontalPieces;
22 verticalPieces++;
23 }
24 }
25 return totalCost; // Return the minimum total cost
26 }
27};
28
1// Function to calculate the minimum cost to divide a chocolate bar
2function minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {
3 // Sort horizontal and vertical cuts in descending order
4 horizontalCut.sort((a, b) => b - a);
5 verticalCut.sort((a, b) => b - a);
6
7 let totalCost = 0; // Total minimum cost
8 let hCutIndex = 0; // Index for horizontal cuts
9 let vCutIndex = 0; // Index for vertical cuts
10 let horizontalSegments = 1; // Number of horizontal segments created
11 let verticalSegments = 1; // Number of vertical segments created
12
13 // Process each cut until all cuts are used
14 while (hCutIndex < m - 1 || vCutIndex < n - 1) {
15 // If no vertical cuts left or current horizontal cut is larger, choose horizontal cut
16 if (vCutIndex === n - 1 || (hCutIndex < m - 1 && horizontalCut[hCutIndex] > verticalCut[vCutIndex])) {
17 totalCost += horizontalCut[hCutIndex] * verticalSegments; // Add cost of horizontal cut
18 horizontalSegments++; // Increase horizontal segments counter
19 hCutIndex++; // Move to next horizontal cut
20 } else {
21 // Otherwise, choose vertical cut
22 totalCost += verticalCut[vCutIndex] * horizontalSegments; // Add cost of vertical cut
23 verticalSegments++; // Increase vertical segments counter
24 vCutIndex++; // Move to next vertical cut
25 }
26 }
27
28 return totalCost; // Return the total minimum cost required
29}
30
Time and Space Complexity
The time complexity of the code is O(m * log m + n * log n)
. This is due to the sorting operations on the horizontalCut
and verticalCut
lists, which each require O(m * log m)
and O(n * log n)
time respectively. The subsequent while-loop contributes O(m + n)
complexity since at most m + n
iterations are needed, but this is dominated by the sorting term.
The space complexity is O(log m + log n)
, which is attributed to the auxiliary space needed for sorting the two lists. Each sort operation can require up to O(log m)
and O(log n)
space, cumulatively.
Learn more about how to find time and space complexity quickly.
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
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!