Problem overview
You get an int[] named cabTripTime, where element i is the number of minutes the i-th cab needs for one trip, and a number of trips n. Cabs work in parallel, each one starts its next trip as soon as it finishes the previous one, and there is no waiting. MinTime returns the smallest number of minutes after which at least n trips are completed in total.
For n = 3 and cabTripTime = [1, 2] the answer is 2. After 1 minute the first cab has done 1 trip and the second none, which is 1 in total. After 2 minutes the first cab has done 2 trips and the second 1, which is 3. For n = 10 and [1, 3, 5, 7] the answer is 7: after 6 minutes the cabs have done 6 + 2 + 1 + 0 = 9 trips, after 7 minutes 7 + 2 + 1 + 1 = 11.
If any cab has a trip time of 0, it finishes every trip instantly and the answer is 0.
Approach: binary search on the time
Instead of simulating the cabs, ask a yes/no question about a candidate time t: can the cabs complete at least n trips within t minutes? A cab with trip time v completes t / v trips (integer division, dropping the unfinished trip). Adding that over all cabs gives the total for t.
The total never goes down as t grows, so the answers to the question look like no, no, no, yes, yes, yes. We want the first yes. That shape is exactly what binary search handles: test the middle time, and discard the half that can't contain the first yes.
The search range runs from 1 to n * min(cabTripTime). The upper end is always enough: the fastest cab alone finishes n trips in n * min minutes. This is why the statement calls T = n * min(cabTripTime) the longest time worth trying. The search takes about log T rounds, and each round adds up m divisions, giving O(m log T).
Take n = 3 and [1, 2]. The range is 1 to 3. The middle, 2, is feasible (3 trips), so record 2 and search 1 to 1. Time 1 gives only 1 trip, so the search ends with 2.
Step by step
The lesson provides MinTime, which does the binary search, and asks for the helper that answers the yes/no question.
- Declare
HasSolution(int[] cabTripTime, int n, int time)aboveMinTime. It returns abool: whetherntrips fit intimeminutes. - Start a counter
sat 0, as along. It counts completed trips, and the last bullet below explains why it needs 64 bits. - Open the loop over the cabs. Write a
foreachovercabTripTimewith anintnamedv, and its closing brace at once. - Add each cab's trips. Inside the loop, add
time / vtos. Integer division truncates, which is right: a trip that isn't finished doesn't count. - Return
s >= n.
The given MinTime then does the rest. It takes the smallest trip time, sets the range to left = 1 and right = n * minTime, and loops while left <= right. For the middle value, if HasSolution says yes, the middle is recorded in result and the search continues on the left half (right = mid - 1) in case an earlier time also works. If it says no, the search moves to the right half (left = mid + 1). The last recorded value is the smallest feasible time.
The solution in C#
class Solution {
public bool HasSolution(int[] cabTripTime, int n, int time) {
long s = 0;
foreach (int v in cabTripTime) {
s += time / v;
}
return s >= n;
}
public int MinTime(int[] cabTripTime, int n) {
int minTime = cabTripTime.Min();
int result = 0, left = 1, right = n * minTime;
while (left <= right) {
int mid = (left + right) >> 1;
if (HasSolution(cabTripTime, n, mid)) {
result = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return result;
}
}
The C# details worth knowing:
- Integer division truncates. With
intoperands,6 / 5is 1,4 / 5is 0, and-7 / 2is -3 (toward zero). Here both operands are non-negative, so it simply drops the fraction. - Dividing by zero throws.
time / vwithv == 0throws aDivideByZeroExceptionforints. The solution avoids it without a check: when some cab has a trip time of 0,Min()returns 0, sorightis 0,left <= rightis false at once, and the method returns 0 beforeHasSolutionis ever called. Min()comes from LINQ. It is an extension method on arrays and lists, inSystem.Linq, so a standalone file needsusing System.Linq;. It throws anInvalidOperationExceptionon an empty array, which the constraints rule out.(left + right) >> 1is the midpoint: a right shift by one bit halves a non-negativeint. Withrightat most 200 million, the sum fits in anint.- Several declarations on one line.
int result = 0, left = 1, right = n * minTime;declares threeintvariables, and the last initializer can usenandminTime. - Integer overflow doesn't throw. By default, an
intthat goes past 2,147,483,647 wraps around to a negative number without any error. This matters fors: it addstime / vover every cab, so with enough cabs the sum leaves theintrange. With a million cabs that each take 1 minute, a time of 1,000,000 minutes gives 1,000,000,000,000 trips: anintcounter would end that loop at -727379968, while thelongends at 1000000000000. That is whysis along. The addition widens theintquotient tolong, ands >= nworks becausenconverts tolongautomatically. With those cabs andn = 2000000,MinTimereturns 2.
Complexity
Let m be the number of cabs and T = n * min(cabTripTime).
Time: O(m log T). The search range has T values and halves every round, so there are about log T rounds. Each round calls HasSolution, which makes one pass over the m cabs. Finding the minimum before the search costs one more pass, O(m).
Space: O(1). The method uses a handful of integers.
Edge cases and common mistakes
- A cab with trip time 0.
[0, 5]withn = 10returns 0. - One cab.
[5]withn = 4returns 20: the cab needs 5 minutes per trip, and the search range is 1 to 20. - One trip.
[3, 3, 3]withn = 1returns 3, since one trip takes 3 minutes on any cab. - Cabs that finish together.
[2, 3]withn = 5returns 6: after 6 minutes the cabs have done 3 + 2 = 5 trips, and after 5 minutes only 2 + 1 = 3. - Stopping at the first
yes. WhenHasSolutionis true, the answer may still be smaller. The search must keep going left and remembermid. - Rounding up. A cab with trip time 3 has completed 1 trip at minute 5, not 2. Rounding the division up would count unfinished trips.
- Searching the trip count. The monotone quantity is time, not the number of trips. The answer is a time.
Testing
n = 3,[1, 2]gives2.n = 10,[1, 3, 5, 7]gives7.n = 10,[0, 5]gives0.n = 4,[5]gives20.n = 5,[2, 3]gives6.n = 2000000with one million cabs that each take 1 minute gives2. This is the case that needs thelongcounter.
