Problem overview
A company has several cabs, and the cab at index i needs cabTripTime[i] minutes for any trip. The function minTime receives the IntArray cabTripTime and the number of trips n, and returns the smallest number of minutes in which n trips can be completed. The cabs work at the same time, and a cab starts its next trip the moment it finishes the previous one.
For n = 3 and [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 finished 2 trips and the second cab 1, which makes 3.
For n = 10 and [1, 3, 5, 7] the answer is 7. After 6 minutes the cabs finish 6 + 2 + 1 + 0 = 9 trips, which is one short. After 7 minutes they finish 7 + 2 + 1 + 1 = 11.
The statement asks for O(m log T) time, where m is the number of cabs and T is n times the fastest trip time, and for O(1) extra space.
Approach: search over the time, not over the trips
Simulating the cabs trip by trip would take about n steps, and n reaches two million. Instead, flip the question. Instead of asking how long n trips take, ask whether t minutes are enough. That question is easy to answer: a cab with trip time v finishes t / v whole trips in t minutes, rounded down because an unfinished trip doesn't count, and the cabs' counts add up.
The answer to "are t minutes enough?" has a useful shape. If 7 minutes are enough, 8 are enough too, since the cabs only finish more trips. So as t grows, the answers go from "no, no, no" to "yes, yes, yes". The smallest t that gives "yes" is the one wanted, and binary search finds it in about log T checks.
The search needs a range. The lower end is 1 minute. For the upper end, the fastest cab alone finishes n trips in n * minTrip minutes, so that time is always enough, and T = n * minTrip can serve as the largest time worth trying. Each check costs one pass over the m cabs, which gives the O(m log T) bound.
Step by step
The lesson provides the binary search in minTime and asks you to write the check, a helper called hasSolution.
- Add
hasSolution(cabTripTime: IntArray, n: Int, time: Int): Boolean. It answers: can the cabs finishntrips withintimeminutes?minTimecalls it with a middle time, then searches the shorter or the longer half. - Create the counter
swithvar, starting at0L, so it is aLong. It counts the trips all cabs finish intimeminutes. The notes after the solution explain why it needs 64 bits. - Open the loop over the cabs with
for (v in cabTripTime)and write its closing brace at once. The variablevis the trip time of one cab. - Inside the loop, add
time / vtos. Dividing twoIntvalues drops the fraction, so a trip that isn't finished doesn't count. The cabs drive in parallel, so their counts are added. - Return
s >= n.truemeans thattimeis enough, so the search tries a shorter time.falsemeans it needs a longer one.
The search in minTime does the rest. It finds the fastest trip time, minTrip, and sets the search range from left = 1 to right = n * minTrip. In each round it checks the middle time. If the middle time is enough, it records it in result and looks at the shorter half, right = mid - 1. If not, it moves to the longer half, left = mid + 1. The last recorded time is the smallest one that works.
The solution in Kotlin
class Solution {
fun hasSolution(cabTripTime: IntArray, n: Int, time: Int): Boolean {
var s = 0L
for (v in cabTripTime) {
s += time / v
}
return s >= n
}
fun minTime(cabTripTime: IntArray, n: Int): Int {
var minTrip = cabTripTime[0]
for (v in cabTripTime) {
minTrip = minOf(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
}
}
return result
}
}
The Kotlin details worth a closer look:
- Integer division.
time / vwith twoIntvalues truncates, so7 / 3is 2. That is exactly the number of whole trips. - Division by zero.
time / 0throws anArithmeticExceptionforInt. A trip time of 0 never reaches the division: thenminTripis 0,rightis 0 and the loop doesn't run, sominTimereturns 0, which is the right answer when a cab finishes trips instantly. Intlimits.n * minTripis at most 200 million by the constraints, andleft + rightat most 400 million, so the midpoint can't overflow. The countersis different: it adds up counts from every cab, so it is aLong, written0L. With a million cabs that each take 1 minute, a time of 1,000,000 minutes gives 1,000,000,000,000 trips. That is far beyond theIntlimit of 2,147,483,647: anIntcounter would hold -727379968 after that loop, while theLongholds 1000000000000. Ins += time / vtheIntresult of the division is widened toLongbefore it is added, ands >= ncompares aLongwith anIntwithout a cast. With these million cabs andn = 2000000,minTimereturns 2.minOf. This standard-library function returns the smaller of two values, so the loop keeps the smallest trip time seen so far.- A
forloop over anIntArray.for (v in cabTripTime)reads the elements directly, without indices. - Function parameters are read-only. The code changes
left,rightandresult, which are localvarvariables, and never the parameters.
Complexity
Let m be the number of cabs and T = n * minTrip.
Time: O(m log T). The search halves the range from T down to nothing, so it makes about log T rounds. Each round calls hasSolution, which makes one pass over m cabs. Finding minTrip costs one more pass, O(m).
Space: O(1). Only a handful of integers are stored.
Edge cases and common mistakes
- One cab.
[5]withn = 4returns 20: the lone cab does the four trips one after another. - One trip.
[2, 2, 2]withn = 1returns 2, since the first trip needs the full 2 minutes on any cab. - A cab with trip time 0.
[0, 3]withn = 4returns 0. - Counting a partial trip. A cab that is halfway through a trip hasn't completed anything. Rounding up would count it and give answers that are too small.
- Returning the wrong end of the search. The answer is the smallest time that works, so a successful check moves
rightdown, and the time is recorded before that. If you moveleftup on a success, you end up with the largest time tried, which is too big. - Using
left < right. When the range is a single time, it would be skipped, and a correct answer could be missed.
Testing
cabTripTime = [1, 2],n = 3gives2.cabTripTime = [1, 3, 5, 7],n = 10gives7.cabTripTime = [5],n = 4gives20.cabTripTime = [0, 3],n = 4gives0.cabTripTime = [20000],n = 10000gives200000000.- One million cabs that each take 1 minute,
n = 2000000, gives2. This is the case that needs theLongcounter.
