Leetcode 1199. Minimum Time to Build Blocks
Problem description
The problem is about finding the minimum time required to build all the blocks provided. You initially start with one worker who can do two tasks:
-
Split into two workers: This increases the number of workers. The time cost for turning one worker into two is provided as an integer, 'split'.
-
Build a block: A worker can build a block and then go home. The time taken to build a block is provided as elements in an array 'blocks'.
The goal is to figure out the minimum time required to build all blocks.
Approach
The problem can be approached using the priority queue data structure. In a priority queue, elements are always returned in priority order (from smallest to largest in this problem).
The building blocks are added into a priority queue where each block is represented by the time needed to build the block.
As long as there is more than one block in the queue, the algorithm continues to loop through, each time taking out the block with the shortest time, splitting a worker and assigning the worker to build the block that takes the longest time.
Finally, the time at the top of the queue is returned.
Walkthrough
Let's take an example where blocks = [1, 2, 3]
and split = 1
.
After pushing all blocks into the priority queue, the queue would look like this: [1, 2, 3]
.
The algorithm will then pop the smallest block, which takes 1
time unit, leaving [2, 3]
. The next smallest block then gets added the split time forming a new block [2 + 1]
becomes [3]
. The priority queue then looks like [3, 3]
.
Next, it removes the smallest block, 3
, leaving [3]
. This time there are no blocks left to split workers, so the queue remains the same.
At the end, the last item in the queue, 3
, is returned as the minimum time to build all blocks.
Python Solution:
1 2python 3import heapq 4 5class Solution: 6 def minBuildTime(self, blocks, split): 7 heapq.heapify(blocks) 8 while len(blocks) > 1: 9 _, x = heapq.heappop(blocks), heapq.heappop(blocks) 10 heapq.heappush(blocks, x + split) 11 return blocks[0]
Java Solution:
1
2java
3class Solution {
4 public int minBuildTime(int[] blocks, int split) {
5 PriorityQueue<Integer> minHeap = new PriorityQueue<>();
6 for (int block : blocks)
7 minHeap.add(block);
8 while (minHeap.size() > 1) {
9 minHeap.poll();
10 int x = minHeap.poll();
11 minHeap.add(x + split);
12 }
13 return minHeap.peek();
14 }
15}
Javascript Solution:
1 2javascript 3class Solution { 4 minBuildTime(blocks, split) { 5 blocks.sort((a, b) => a - b); 6 while (blocks.length > 1) { 7 let x = blocks.pop(); 8 blocks[blocks.length - 1] = Math.max(blocks[blocks.length - 1], x + split); 9 } 10 return blocks[0]; 11 } 12}
C++ Solution:
1
2cpp
3class Solution {
4public:
5 int minBuildTime(vector<int>& blocks, int split) {
6 priority_queue<int, vector<int>, greater<int>> minHeap;
7 for (const int block : blocks)
8 minHeap.push(block);
9 while (minHeap.size() > 1) {
10 minHeap.pop();
11 const int x = minHeap.top();
12 minHeap.pop();
13 minHeap.push(x + split);
14 }
15 return minHeap.top();
16 }
17};
C# Solution:
1
2csharp
3public class Solution {
4 public int MinBuildTime(int[] blocks, int split) {
5 var minHeap = new SortedSet<(int, int)>();
6 foreach (int block in blocks) {
7 minHeap.Add((block, block));
8 }
9 while (minHeap.Count > 1) {
10 (int _, int x) = minHeap.Min;
11 minHeap.Remove(minHeap.Min);
12 minHeap.Add((x + split, x + split));
13 }
14 return minHeap.Min.Item1;
15 }
16}
In all above solutions, at the start we are queuing all blocks into a priority queue with respect to the time to build. The steps are repeated until there is only one block left in the queue, each time using a queue operation that takes time O(log n). The algorithm runs in O(n log n) time since it needs to push n
blocks into the heap and perform n - 1
operations, where n
is the length of the 'blocks' array. The total time complexity is O(n log n).These solutions use common elements in data structure algorithms, such as heaps, queues, and sorting, which are essential strategies when tackling optimization problems. In this case, we are optimizing for the minimum time to build all blocks, where the time to build each block varies and there is a cost in time for splitting a worker into two.
In all versions of the algorithm, we first put all our blocks into a queue or heap, with the time to build each block being the key value used to determine the order of elements in our queue or heap. This allows us to always extract the block with the minimum build time, which follows our optimization strategy of minimizing the total time spent.
After we have our blocks in order, we then enter the main part of our algorithm: a loop that continues until we only have one block left. In this loop, we take out the block that takes the shortest time to build and then add a new block to our queue or heap that represents the remaining work to be done.
The time of this remaining block is the time taken to split a worker plus the time for the next shortest block (since one of the split workers will take this block). We repeat this loop until we have built all blocks -- the time of the remaining block in our queue or heap is then the minimum time needed to build all blocks.
In terms of time complexity, this solution is O(n log n) where n is the number of blocks. This is because the time complexity of heapify, adding an item, and removing an item from a heap or queue are all O(log n) and we do these operations n times.
This solution teaches us the usefulness of the heap or priority queue data structures when solving optimization problems. The ability to always extract the minimum or maximum element in a sequence in a short amount of time can be very useful, especially in problems where we want to minimize or maximize something.
Got a question?ย Ask the Teaching Assistantย anything you don't understand.
Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.