Problem overview
A fleet of cabs must complete n trips. Cab i needs cabTripTime[i] minutes for any trip, starts the next one right away, and all cabs work at the same time. The method minTime receives cabTripTime and n and returns the smallest number of minutes after which at least n trips are finished.
With n = 3 and cabTripTime = [1, 2] the answer is 2. After 1 minute only the first cab has finished a trip, which makes 1 trip. After 2 minutes the first cab has made 2 trips and the second cab 1, which makes 3. With n = 10 and [1, 3, 5, 7] the answer is 7: after 6 minutes the cabs have finished 6 + 2 + 1 + 0 = 9 trips, and after 7 minutes they have finished 7 + 2 + 1 + 1 = 11.
The statement adds a special case: a cab with trip time 0 finishes any number of trips instantly, so the answer is then 0. The target running time is O(m log T) for m cabs, with T = n * min(cabTripTime), and O(1) extra space.
Approach: search the time, not the trips
Simulating the cabs trip by trip would be far too slow, since n can be 2,000,000. Turn the problem around: instead of asking how long the trips take, ask whether a given time is enough.
For a time t, cab i finishes floor(t / cabTripTime[i]) whole trips, and the cabs work in parallel, so the fleet finishes the sum of those counts. If the sum is at least n, the time is enough.
That question is monotonic. If t minutes are enough, then t + 1 minutes are enough too, since no cab finishes fewer trips given more time. So the times split into a block of "not enough" followed by a block of "enough", and binary search can find where the second block starts. Each check costs O(m), and the search needs about log T checks.
The search range needs two bounds. The lower bound is 1. The upper bound is n * min(cabTripTime): the fastest cab alone could do all n trips in that time, so the answer can't be larger.
On n = 10 and [1, 3, 5, 7], the range is 1 to 10. The middle, 5, gives 5 + 1 + 1 + 0 = 7 trips, which is not enough, so the search moves to 6 to 10. The middle, 8, gives 8 + 2 + 1 + 1 = 12, which is enough, so 8 is remembered and the search moves to 6 to 7. The middle, 6, gives 9, not enough, so the range is just 7. It gives 11, so the answer is 7.
Step by step
The lesson supplies the binary search in minTime and asks for the helper it calls.
- Declare
hasSolutionwith the parameterscabTripTime,nandtime, returning aboolean. It answers whethertimeminutes are enough forntrips. - Create a counter
swithlet, starting at 0. It counts the trips finished by all cabs. - Loop over the cabs with
for...of, naming each trip timev. - Add the cab's whole trips:
time / vrounded down withMath.floor. The division keeps the fraction, and a trip that hasn't finished doesn't count. - Return
s >= n.truetells the search to try a shorter time, andfalsetells it to try a longer one.
The binary search in minTime follows the usual pattern for "find the smallest value that works". It finds the shortest cab time, sets left = 1 and right = n * minTrip, and tries the middle. When the middle works, it is saved in result and the search continues to its left for something smaller. Otherwise the search continues to its right.
The solution in TypeScript
class Solution {
hasSolution(cabTripTime: number[], n: number, time: number): boolean {
let s = 0;
for (const v of cabTripTime) {
s += Math.floor(time / v);
}
return s >= n;
}
minTime(cabTripTime: number[], n: number): number {
let minTrip = cabTripTime[0];
for (const v of cabTripTime) {
minTrip = Math.min(minTrip, v);
}
let result = 0, left = 1, right = n * minTrip;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (this.hasSolution(cabTripTime, n, mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}
The TypeScript details to know:
- Division gives a fraction. In TypeScript,
7 / 2is3.5, not 3, becausenumberis a double.Math.floorturns it into the whole-trip count, and the same trick computes the midpoint. - No overflow in the midpoint. In languages with 32-bit integers,
(left + right) / 2can overflow. Anumberholds integers exactly up to 2^53, and here the values stay within a few hundred million. - The zero case works by itself. If some cab has trip time 0,
minTripis 0 andright = n * 0 = 0. Sinceleft = 1is already greater thanright, the loop never runs and the method returns the initialresultof 0. Dividing by zero would not throw in JavaScript anyway:Math.floor(7 / 0)isInfinity. It's the loop that never reaches the helper. - Several declarations in one
let.let result = 0, left = 1, right = n * minTrip;declares three variables, and each is inferred as anumber. this.hasSolution. A method must be called throughthisfrom inside another method of the same class, as inthis.hasSolution(...).- Annotations are erased. The parameter types describe the expected arguments to the compiler only. Nothing checks them at run time.
Complexity
Time: O(m log T). Finding the shortest trip time is one pass over the m cabs. The search range has about T = n * min(cabTripTime) values and halves on every step, so it takes about log T steps, and each step calls hasSolution, which makes one pass over the cabs.
Space: O(1). The method keeps a few numbers, whatever the sizes of n and the array.
Edge cases and common mistakes
- One cab.
[4]withn = 1gives 4, and[5]withn = 3gives 15: the one cab does all the trips one after another. - Equal cabs.
[2, 2]withn = 5gives 6. Each cab does 3 trips in 6 minutes, so 6 trips in all. After 4 minutes the two cabs have finished only 4 trips. - A zero trip time.
[0, 5]withn = 4gives 0. - Forgetting
Math.floor. Without it,time / vcounts fractions of a trip, so a time can look sufficient before the last trips have really finished. - Returning the middle at once. When
hasSolutionis true, the middle may not be the smallest working time. The search saves it and keeps looking left. - Dropping the
=inleft <= right. The range of one value would never be tested, and the search could skip the answer: for[1, 3, 5, 7]andn = 10the last range is just 7. - Too small an upper bound. The upper bound must be at least the true answer.
n * min(cabTripTime)always is, since the fastest cab alone could finish allntrips in that time. A smaller guess, such as the fastest trip time alone, would cut off the answer for[1, 2]andn = 3.
Testing
n = 3,cabTripTime = [1, 2]gives2.n = 10,cabTripTime = [1, 3, 5, 7]gives7.n = 1,cabTripTime = [4]gives4.n = 5,cabTripTime = [2, 2]gives6.n = 4,cabTripTime = [0, 5]gives0.
