Start learning free

LESSON 287 OF 442 · BINARY SEARCH

Uber Rides in Go: O(m log T) Binary Search on the Answer

Practice O(n log T) Read the full explanation

Uber has several cabs. The ith cab takes cabTripTime[i] minutes to complete any trip. Your task is to find the minimum time it will take Uber to get n trips completed with these cabs. You can assume there is no waiting time in-between the trips.

Note: Different cabs can take trips simultaneously (at any time, there can be more than one cab with an ongoing trip)

Example 1:

Input: n = 3, cabTripTime = [1, 2]
Output: 2
Explanation:
Trips can be managed like this:
- Trip 1: first cab from t = 0 to t = 1;
- Trip 2: second cab from t = 0 to t = 2;
- Trip 3: first cab from t = 1 to t = 2.

All the trips can be completed in 2 minutes. In 1 minute the cabs finish only 1 + 0 = 1 trip, so the answer is 2.

Example 2:

Input: n = 10, cabTripTime = [1, 3, 5, 7]
Output: 7
Explanation:
Trips can be managed like this:
- Trip 1: 1st cab from t = 0 to t = 1;
- Trip 2: 2nd cab from t = 0 to t = 3;
- Trip 3: 3rd cab from t = 0 to t = 5;
- Trip 4: 4th cab from t = 0 to t = 7;
- Trip 5: 1st cab from t = 1 to t = 2;
- Trip 6: 1st cab from t = 2 to t = 3;
- Trip 7: 1st cab from t = 3 to t = 4;
- Trip 8: 2nd cab from t = 3 to t = 6;
- Trip 9: 1st cab from t = 4 to t = 5;
- Trip 10: 1st cab from t = 5 to t = 6.

All the trips can be completed in 7 minutes. In 6 minutes the cabs finish at most 6 + 2 + 1 + 0 = 9 trips, so the answer is 7.

Constraints:

  • 1 <= cabTripTime.length <= 106
  • 0 <= cabTripTime[i] <= 2 * 104
  • 1 <= n <= 2 * 106
  • n * min(cabTripTime) <= 2 * 108
  • A cab whose trip time is 0 finishes any number of trips instantly, so then the answer is 0.

Note:

Your algorithm should run in O(m log T) time, where m is the number of cabs and T = n * min(cabTripTime) is the longest time worth trying, and use O(1) extra space.


STEP 1 OF 5
    Ctrl + Enter to check

    GO · BINARY SEARCH

    Uber Rides in Go, explained

    Problem overview

    The function minTime receives a slice cabTripTime, where cab i needs cabTripTime[i] minutes for any trip, and a number of trips n. All cabs can drive at the same time and there is no waiting between trips. The function returns the least number of minutes in which n trips can be finished.

    With n = 3 and cabTripTime = [1, 2] the answer is 2. After 1 minute only the first cab has finished a trip, which is 1 trip in total. After 2 minutes the first cab has made 2 trips and the second 1, which is 3. With n = 10 and [1, 3, 5, 7] the answer is 7: after 6 minutes the cabs have done at most 6 + 2 + 1 + 0 = 9 trips, and after 7 minutes they have done 7 + 2 + 1 + 1 = 11.

    The statement asks for O(m log T) time, where m is the number of cabs and T = n * min(cabTripTime), using O(1) extra space. A cab with trip time 0 finishes any number of trips instantly, so the answer is then 0.

    Approach: binary search on the time

    It is hard to schedule the trips directly, but it is easy to ask a yes-or-no question: can the cabs finish n trips within t minutes? A cab with trip time v finishes t / v whole trips in t minutes, rounded down, because a trip cut off by the deadline does not count. Add that over all cabs, and the answer to the question is whether the total reaches n.

    That check is monotonic. If t minutes are enough, so is any larger time, since the cabs only complete more trips. A monotonic yes-or-no question over a range of integers is exactly what binary search handles: find the smallest t for which the answer is yes.

    The search range needs a start and an end. The time cannot be below 1 (the trip times are positive in the cases that reach the search). It never needs to exceed n * min(cabTripTime), because the fastest cab alone finishes n trips in that time. That upper bound is the lesson's T. A linear scan over all times from 1 to T would cost O(m * T); halving the range each time brings it to O(m log T).

    Step by step

    The starter code already contains the search; the lesson's steps write the feasibility check.

    1. Define a closure named hasSolution inside minTime with the parameters (cabTripTime []int, n int, time int), returning bool. The binary search will call it to test each candidate time.
    2. Declare s, the total number of finished trips, starting at 0.
    3. Loop over the cabs with range, ignoring the index with _ and naming each trip time v: how many minutes one trip of that cab takes.
    4. Add time / v to s inside the loop. Integer division throws away the unfinished part of the last trip, which is what is wanted.
    5. Return s >= n: the time is enough when at least n trips are finished.

    The search around it works like this. It finds the smallest minute count for which the check is true: left and right bound the candidates, mid is tested, and a successful mid is recorded in result before the search moves left to look for something smaller. A failed mid moves the search right.

    The solution in Go

    func minTime(cabTripTime []int, n int) int {
        hasSolution := func(cabTripTime []int, n int, time int) bool {
            s := 0
            for _, v := range cabTripTime {
                s += time / v
            }
            return s >= n
        }
    
        minTrip := cabTripTime[0]
        for _, v := range cabTripTime {
            minTrip = min(minTrip, v)
        }
        result, left, right := 0, 1, n*minTrip
        for left <= right {
            mid := (left + right) >> 1
            if hasSolution(cabTripTime, n, mid) {
                result = mid
                right = mid - 1
            } else {
                left = mid + 1
            }
        }
        return result
    }
    

    Go details worth a closer look:

    • Closures. hasSolution := func(...) bool { ... } is a function value stored in a variable. It is declared before use and could be called like any function.
    • Parameter names inside the closure. The closure's cabTripTime and n shadow the outer ones. The call passes the same values in, so they agree.
    • Integer division. For positive integers time / v rounds down, so 7 / 2 is 3.
    • Multiple assignment. result, left, right := 0, 1, n*minTrip declares all three at once.
    • (left + right) >> 1 is the midpoint, a right shift by one bit, which divides by two. The sum is at most about 4 × 10^8 by the constraints, far below the 64-bit limit.
    • Built-in min. Since Go 1.21, min is built in; it finds the fastest cab's trip time.
    • result starts at 0. If some cab has trip time 0, then right is 0, the loop never runs, and the function returns 0 without ever dividing by zero.

    Complexity

    Time: O(m log T). The search range has T = n * min(cabTripTime) values, so it takes about log T rounds, and each round runs hasSolution in O(m). The scan for the minimum adds another O(m).

    Space: O(1). A few integers and the closure are all that is stored.

    Edge cases and common mistakes

    • A cab with trip time 0. The answer is 0. Dividing by 0 would panic with an integer divide by zero error, but hasSolution is never called in that case, as shown above.
    • One cab. cabTripTime = [5] with n = 4 gives 20: the only cab drives all the trips.
    • Fewer than a full trip. With [3, 3] and n = 3 the answer is 6, not 4.5: after 3 minutes two trips are done, and the third needs the next full 3 minutes. Time is whole minutes, and unfinished trips never count.
    • Recording the answer. When the check succeeds, result is saved and the search continues on the left half. Returning at the first success would give a time that may not be the smallest.
    • The loop condition. left <= right keeps the last remaining candidate in play. With < the case where the answer equals left and right would be missed.
    • Using the slowest cab. The upper bound comes from the fastest cab, which gives the smallest safe limit. Using the slowest cab's time would still be correct but a much larger range.

    Testing

    • cabTripTime = [1, 2], n = 3 gives 2.
    • cabTripTime = [1, 3, 5, 7], n = 10 gives 7.
    • cabTripTime = [5], n = 4 gives 20.
    • cabTripTime = [3, 3], n = 3 gives 6.
    • cabTripTime = [0, 3], n = 7 gives 0.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free