Problem overview
The method minTime receives cabTripTime, an Array[Int] where element i is the number of minutes cab i needs for one trip, and n, the number of trips that must be completed. The cabs work at the same time and never wait between trips. The method returns the smallest number of minutes after which at least n trips are done.
With n = 3 and cabTripTime = [1, 2], after 1 minute the first cab has finished 1 trip and the second none, so 1 trip in total. After 2 minutes the first cab has finished 2 trips and the second 1, so 3 trips in total, and the answer is 2. With n = 10 and [1, 3, 5, 7], six minutes give 6 + 2 + 1 + 0 = 9 trips, which is not enough, and seven minutes give 7 + 2 + 1 + 1 = 11, so the answer is 7.
The statement asks for O(m log T) time, where m is the number of cabs and T = n * min(cabTripTime), and for constant extra space. A cab whose trip time is 0 finishes any number of trips instantly, and then the answer is 0.
Approach: binary search on the answer
Searching minute by minute would work but is slow. The problem has a structure that allows something better. Define a yes-or-no question: can the cabs finish n trips within t minutes? If the answer is yes for some t, it is also yes for every larger t, since extra time can only add trips. So the answers run no, no, no, yes, yes, yes as t grows. The task is to find the first yes, and binary search does that by checking the middle time and discarding half of the range each time.
The question is easy to answer. Within t minutes, a cab with trip time v finishes t / v whole trips, and integer division drops an unfinished trip, which doesn't count. Adding this over all cabs gives the total, which is compared with n.
The range to search is from 1 to n * min(cabTripTime). That upper limit always works: the fastest cab alone finishes n trips in that time.
A worked search for n = 3 and [1, 2]: the range is 1 to 3. The middle is 2, and 2 / 1 + 2 / 2 = 3, which is enough, so remember 2 and search 1 to 1. The middle is 1, and 1 / 1 + 1 / 2 = 1, which isn't enough, so search 2 to 1, which is empty. The answer is 2.
Step by step
The lesson provides the search in minTime and has you write the helper it calls, hasSolution.
- Add the helper
hasSolution(cabTripTime: Array[Int], n: Int, time: Int): Boolean, with a body in braces after=. It answers whethertimeminutes are enough. - Create a counter
swithvar, starting at0L, so it is aLong. It counts the trips all cabs finish withintime, and it grows once per cab. - Open the loop over the cabs with
for (v <- cabTripTime), wherevis that cab's minutes per trip, and write its closing brace at once. - Inside the loop, add the whole trips of this cab with
s += time / v. The trips of different cabs add up because the cabs work simultaneously. - Return
s >= nas the last expression. Atruemeans the time is enough, so the search tries a shorter time. Afalsemeans it needs a longer one.
In the search itself, minTime finds the fastest cab with a loop, sets left to 1 and right to n * minTrip, and keeps a result. For each middle time it asks hasSolution. On a true it records the time as a candidate answer and moves right below it, to look for something shorter. On a false it moves left above it.
The solution in Scala
class Solution {
def hasSolution(cabTripTime: Array[Int], n: Int, time: Int): Boolean = {
var s = 0L
for (v <- cabTripTime) {
s += time / v
}
s >= n
}
def minTime(cabTripTime: Array[Int], n: Int): Int = {
var minTrip = cabTripTime(0)
for (v <- cabTripTime) {
minTrip = math.min(minTrip, v)
}
var result = 0
var left = 1
var right = n * minTrip
while (left <= right) {
val mid = (left + right) / 2
if (hasSolution(cabTripTime, n, mid)) {
result = mid
right = mid - 1
} else {
left = mid + 1
}
}
result
}
}
The Scala details worth knowing:
- Integer division truncates. With two
Ints,7 / 3is 2, sotime / vis exactly the count of finished trips. Noflooris needed. - Division by zero throws. Dividing an
Intby a variable that holds 0 throws anArithmeticExceptionwith the message/ by zero. A cab with trip time 0 would do that insidehasSolution. The solution never gets there: when the fastest cab takes 0 minutes,rightisn * 0 = 0, the conditionleft <= right(1 <= 0) is false at once, and the method returns the initialresult, 0. varversusval.s,minTrip,result,leftandrightall change, so they arevars.midis fixed inside one pass of the loop, so it is aval.- The last expression.
hasSolutionends withs >= nandminTimewithresult. Neither needsreturn. (left + right) / 2. This could overflowIntif both values were near the top of the range, about 2.1 billion. Here the constraints keeprightat or below 200,000,000, so the sum fits.- The type of the sum. The counter
sis aLong, written0L. With a very large number of cabs, the total oftime / vcan exceed theIntrange (largest value 2,147,483,647), and anIntwraps silently. With 1,000,000 cabs that each take 1 minute, a time of 1,000,000 minutes gives a total of 1,000,000,000,000: anIntcounter would end that loop at -727379968, while theLongends at 1000000000000. Ins += time / vtheIntquotient is widened toLong, ands >= ncompares aLongwith anIntdirectly. With those cabs andn = 2000000,minTimereturns 2.
Complexity
Time: O(m log T). The search range has T = n * min(cabTripTime) values, and each iteration halves it, so there are about log T iterations. Each one calls hasSolution, which loops once over the m cabs. Finding the minimum adds one more pass of O(m).
Space: O(1). The code uses a handful of integers and does not copy the array.
Edge cases and common mistakes
- A single cab. With
[5]andn = 1the range is 1 to 5, and the answer is 5. With[3]andn = 4the answer is 12, which isn * 3, the top of the range. - Equal cabs.
[2, 2]withn = 3gives 4: after 2 minutes two trips are done, and the third needs another round. - A zero trip time.
[0, 5]withn = 4gives 0, and no division by zero happens, because the loop never runs. - Using
left < right. A range with one value, like 2 to 2, would never be checked, and the answer could be missed. Keep<=. - Stopping at the first yes. Finding some time that works is not enough: the task asks for the smallest. After a
true, record the time and keep searching the shorter half. - Counting a partial trip. A trip that isn't finished within the time doesn't count. Integer division handles this. Rounding up would give wrong totals.
- Searching from the largest cab time. The upper limit must come from the fastest cab. The slowest cab's time can be much larger than needed, and a range from the fastest cab is always large enough.
Testing
n = 3,[1, 2]gives2.n = 10,[1, 3, 5, 7]gives7.n = 4,[0, 5]gives0.n = 1,[5]gives5.n = 4,[3]gives12.n = 3,[2, 2]gives4.n = 2000000with one million cabs that each take 1 minute gives2. This is the case that needs theLongcounter.
