Problem overview
A factory has workers, and times[i] is the number of time units worker i needs to pack one product. They all work at once. The method minTime returns the smallest time in which they pack at least K products together.
In the example, times = [4, 7, 3, 6, 7, 1] and K = 60. In 30 units the workers pack 7 + 4 + 10 + 5 + 4 + 30 = 60 products. In 29 units they pack only 57. The answer is 30.
The statement asks for O(n * log(k * T)) time and O(1) extra space, where T is the time of the fastest worker.
Approach: binary search on the answer
A worker who needs t units per product packs Math.floor(time / t) products in time units. Summing that over the workers gives the total for a given time, and the total never decreases as time grows. So the times split into two groups: the short ones, which pack fewer than K products, and the long ones, which pack at least K. The answer is the first time of the second group.
Checking one time costs O(n). The simple approach in the previous lesson checks 0, 1, 2, ... in turn. Because the groups are ordered, binary search can find the boundary instead, by checking the middle time and discarding half of the range.
The search needs a range to start from:
- The lower end is 1, since 0 units pack nothing.
- The upper end is
minValue * K, whereminValueis the smallest entry oftimes. The fastest worker alone packsKproducts in that time, so the answer can't be larger.
For times = [2, 3] and K = 5 the range is 1 to 10. The middle is 5, which packs 2 + 1 = 3 products, too few, so the search moves to 6 to 10. The middle is 8, which packs 4 + 2 = 6, enough, so it is recorded and the search moves to 6 to 7. The middle is 6, which packs 3 + 2 = 5, enough, so 6 is recorded and the range becomes 6 to 5, which is empty. The answer is 6.
Step by step
- Find the fastest worker. Start
minValueattimes[0]and, with afor...ofloop, update it withMath.min(minValue, t). - Set the range. Declare
leftandrightin onelet, as1andminValue * K. - Prepare the answer. Declare
timeas 0. It will hold the smallest time found so far that works. - Loop while
left <= right. - Take the middle as
const mid = Math.floor((left + right) / 2). - If
this.countProducts(times, mid) < K, the time is too short, so setleft = mid + 1. - Otherwise the time is enough. Record it with
time = mid, then look for something shorter withright = mid - 1. Recording first matters: the loop may end on a time that was too short, and the last good one must already be saved. - Return
time. - Write the helper
countProducts(times: number[], time: number): numberaboveminTime. - Start
productsat 0, loop withfor (const t of times)and addMath.floor(time / t)for each worker. - Return
products.
The solution in TypeScript
class Solution {
countProducts(times: number[], time: number): number {
let products = 0;
for (const t of times) {
products += Math.floor(time / t);
}
return products;
}
minTime(times: number[], K: number): number {
let minValue = times[0];
for (const t of times) {
minValue = Math.min(minValue, t);
}
let left = 1, right = minValue * K;
let time = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (this.countProducts(times, mid) < K) {
left = mid + 1;
} else {
time = mid;
right = mid - 1;
}
}
return time;
}
}
Some TypeScript details:
- Division keeps the fraction.
numberis a double, so29 / 7is about4.14.Math.floorturns it into the 4 whole products. - Several variables in one
let.let left = 1, right = minValue * K;declares both, each with its own initial value. constformid. Every iteration creates a newmid, so it never needs to be reassigned.- Large values are safe. The upper bound
minValue * Kis a double, and it stays exact up to 2^53, about 9 quadrillion. Sums and midpoints cannot overflow like a 32-bit integer in other languages. - Methods through
this.countProductsbelongs to the class, so the call isthis.countProducts.
Complexity
Time: O(n * log(k * T)). The range holds about minValue * K times, and each loop pass halves it, so there are about log(minValue * K) passes. Each pass calls countProducts, which costs O(n). Finding minValue is one extra O(n) pass. With K equal to a billion and a fastest worker who needs 1 unit, the search takes about 30 passes, where the one-unit-at-a-time version needs up to a billion.
Space: O(1). A few numbers, whatever the input size.
Edge cases and common mistakes
- Not saving the good time. If the answer is not recorded in the
elsebranch, the loop ends with no record of the last time that worked. Savingmidthere removes any off-by-one question at the end. - Upper bound too small. A bound below the true answer makes the search return 0. The fastest worker alone always meets
KatminValue * K, so that bound is safe. - One worker. For
times = [5]andK = 3the range is 1 to 15, and the result is 15. - Using
<=instead of<in the check. Then exactlyKproducts would count as too few, and the answer would come out too long. - Forgetting
Math.floorincountProducts. Fractions of a product would be counted. - Using
/for the midpoint without rounding. A fractionalmidwould be tested and could be saved as the answer, such as29.5.
Testing
times = [4, 7, 3, 6, 7, 1],K = 60gives30.times = [2, 3],K = 5gives6.times = [5],K = 3gives15.times = [4, 7, 3, 6, 7, 1],K = 1gives1.