Start learning free

LESSON 288 OF 443 · BINARY SEARCH

Minimum Time for Cab Trips in Swift: 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

    SWIFT · BINARY SEARCH

    Uber Rides in Swift, explained

    Problem overview

    A fleet has several cabs. Cab i needs cabTripTime[i] minutes for any trip, and cabs drive at the same time. The method minTime receives cabTripTime and the number of trips n, and returns the smallest number of minutes after which the cabs can have finished n trips in total. A cab starts its next trip the moment it ends one.

    With n = 3 and cabTripTime = [1, 2], the answer is 2: the first cab finishes two trips (at minutes 1 and 2) and the second finishes one (at minute 2). After 1 minute only one trip is done. 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, which is not enough, and after 7 minutes they have finished 7 + 2 + 1 + 1 = 11. A cab with trip time 0 completes any number of trips instantly, so the answer is 0 then.

    Approach: search the time, not the schedule

    Scheduling trips to cabs one by one sounds hard. The question becomes easy if you flip it: given a time t, how many trips can the cabs finish within t minutes? Cab i finishes t / cabTripTime[i] whole trips (integer division drops an unfinished trip), and the cabs work in parallel, so the total is the sum over the cabs.

    That count never goes down as t grows, so the times split into two groups: too short at the start, enough from some point on. The answer is the first time in the second group, which is exactly what binary search finds. Each step asks "are mid minutes enough?" and discards half of the possible times.

    The search range is 1 to n * minTrip, where minTrip is the shortest trip time. The upper end is always enough: the fastest cab alone finishes n trips in that time. For n = 3 and [1, 2] the range is 1 to 3. The middle is 2, which gives 2 + 1 = 3 trips, enough, so the search tries the lower half. The time 1 gives 1 trip, not enough, so the answer is 2.

    Step by step

    1. Add a helper hasSolution, taking the trip times, n and a time, and returning Bool. The _ labels let minTime call it without argument names. It answers: can the cabs finish n trips within time minutes?
    2. Start a counter s at 0 with var. It counts the trips all the cabs finish. Swift's Int is 64 bits, so it needs no special type.
    3. Open the loop over the cabs with for v in cabTripTime, where v is one cab's trip time, and write its closing brace at once.
    4. Inside the loop, add time / v to s. Integer division drops the fraction, so a trip that isn't finished doesn't count.
    5. Return s >= n. true means the time is enough, so the search can try shorter; false means it must try longer.

    The remaining part of minTime is the binary search itself: it finds the shortest trip time, sets left = 1 and right = n * minTrip, and loops while left <= right. On a success it records mid in result and moves right below it; on a failure it moves left above it.

    The solution in Swift

    class Solution {
        func hasSolution(_ cabTripTime: [Int], _ n: Int, _ time: Int) -> Bool {
            var s = 0
            for v in cabTripTime {
                s += time / v
            }
            return s >= n
        }
    
        func minTime(_ cabTripTime: [Int], _ n: Int) -> Int {
            var minTrip = cabTripTime[0]
            for v in cabTripTime {
                minTrip = min(minTrip, v)
            }
            var result = 0
            var left = 1
            var right = n * minTrip
            while left <= right {
                let mid = (left + right) / 2
                if hasSolution(cabTripTime, n, mid) {
                    result = mid
                    right = mid - 1
                } else {
                    left = mid + 1
                }
            }
            return result
        }
    }
    

    Swift details worth knowing:

    • Integer division. time / v divides two Int values and truncates toward zero, so 7 / 2 is 3 and 1 / 3 is 0. That is the number of whole trips.
    • Division by zero crashes. Dividing an Int by 0 stops the program in Swift. hasSolution is safe because a zero trip time makes minTrip 0, so right is 0, the loop condition 1 <= 0 is false, and minTime returns the initial result of 0 without calling the helper.
    • Overflow. Int is 64 bits and arithmetic that overflows crashes instead of wrapping. The constraint n * min(cabTripTime) <= 2 * 10^8 keeps right and left + right far from that limit. The counter s can grow much larger: with a million cabs that each take 1 minute, a time of 1,000,000 minutes gives 1,000,000,000,000 trips, which a 64-bit Int holds without trouble. With those cabs and n = 2000000, minTime returns 2.
    • The helper is a method. Inside minTime it is called as hasSolution(cabTripTime, n, mid) without self., which Swift allows in a class method.
    • Constants. mid is a let inside the loop: it is computed once per pass. left, right and result change, so they are var.

    Complexity

    Time: O(m log T), where m is the number of cabs and T = n * minTrip. The binary search takes about log T rounds and every round calls hasSolution, which makes one pass over the m cabs. Finding minTrip costs one more pass.

    Space: O(1). A handful of integers; the array is passed along, not copied into a new one.

    Edge cases and common mistakes

    • A zero trip time. [0, 3] with n = 5 gives 0, as the constraints say.
    • One cab. [5] with n = 4 gives 20, which is n times its trip time. A single trip with [4, 7] and n = 1 gives 4, the shortest trip.
    • Dividing the wrong way. v / time would count something unrelated; the number of trips in time minutes is time / v.
    • Stopping at the first success. A time that works doesn't have to be the smallest one. The search keeps looking at the lower half after every success and keeps the last mid that worked.
    • Starting the range at 0. With zero trip times excluded, 0 minutes can't finish any trips when n is at least 1, so the search starts at 1.
    • Using left < right. With a strict comparison the case where both ends meet is never tested, and answers such as [3] with n = 1 (answer 3, the top of the range) would be missed.
    • Counting more than needed. The count may overshoot n: the condition is s >= n, not s == n.

    Testing

    • n = 3, [1, 2] gives 2.
    • n = 10, [1, 3, 5, 7] gives 7.
    • n = 4, [5] gives 20.
    • n = 1, [3] gives 3, and n = 1, [4, 7] gives 4.
    • n = 5, [0, 3] gives 0.
    • n = 2000000 with one million cabs that each take 1 minute gives 2.
    Keep going with SwiftAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free