Start learning free

LESSON 283 OF 438 · BINARY SEARCH

Uber Rides in JavaScript: 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

    JAVASCRIPT · BINARY SEARCH

    Uber Rides in JavaScript, explained

    Problem overview

    The method minTime receives cabTripTime, an array where entry i is the number of minutes cab i needs for any trip, and n, the number of trips to complete. It returns the minimum time in which the cabs can complete all n trips. Cabs work at the same time, and a cab starts a new trip the moment it finishes the previous one.

    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 trip 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: at 6 minutes the cabs finish at most 6 + 2 + 1 + 0 = 9 trips, and at 7 minutes they finish 7 + 2 + 1 + 1 = 11.

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

    Approach: binary search on the time

    Simulating the cabs trip by trip would be slow. A better question is the reverse one: given a time t, how many trips can the cabs finish? Cab i finishes floor(t / cabTripTime[i]) trips, and the totals add up because cabs don't wait for each other.

    That count never decreases as t grows. If t minutes are enough for n trips, so is any larger time. So the times split into a block that are too small followed by a block that are enough, and the answer is the first time in the second block. That is a binary search, not over an array but over the possible answers.

    The search range is easy to bound. The fastest cab alone finishes n trips in n * min(cabTripTime) minutes, so the answer is at most that. The answer is at least 1 when all trip times are positive.

    For n = 3 and [1, 2], the range is 1 to 3. The middle is 2, which is enough (3 trips), so remember 2 and look lower. The middle of 1 to 1 is 1, which gives only 1 trip, so look higher. The range is empty, and the remembered answer is 2.

    Checking one time costs O(m), and the range has T values, so the search needs about log T checks: O(m log T) in total.

    Step by step

    The lesson writes the feasibility check first, as a helper arrow function inside minTime, then the search around it.

    1. Define hasSolution with const as an arrow function taking (cabTripTime, n, time), as the first line of minTime. It answers whether time minutes are enough for n trips, and the binary search will call it. It has to come before the search, because a const can't be used above the line that creates it.
    2. Declare s with let, starting at 0. It counts the trips finished so far.
    3. Loop over cabTripTime with for...of, naming each trip time v. Each pass looks at one cab, and v is how many minutes one of its trips takes.
    4. Add Math.floor(time / v) to s inside the loop. A cab with trip time v completes that many whole trips in time minutes. JavaScript's / gives a fractional result, so Math.floor turns it into a whole number of trips.
    5. Return s >= n. The time is enough when the cabs finish at least n trips; finishing extra trips is fine.

    The rest of the method is the search itself. The shortest trip time is found with reduce and Math.min. The search then keeps left and right as the smallest and largest time still possible, starting at 1 and n * minTime, and result as the best answer found so far. Each pass takes the middle time mid. If hasSolution says mid is enough, it saves mid in result and continues in the lower half with right = mid - 1. Otherwise it continues in the upper half with left = mid + 1. When the range is empty, result is the smallest time that works.

    The solution in JavaScript

    class Solution {
        /**
        * @param {number[]} cabTripTime
        * @param {number} n
        * @return {number}
        */
        minTime(cabTripTime, n) {
            const hasSolution = (cabTripTime, n, time) => {
                let s = 0
                for (let v of cabTripTime) {
                    s += Math.floor(time / v)
                }
                return s >= n
            }
            
            let minTime = cabTripTime.reduce((acc, val) => acc = Math.min(acc, val), cabTripTime[0])
            let result = 0, left = 1, right = n * minTime;
            while (left <= right) {
                let mid = (left + right) >> 1
                if (hasSolution(cabTripTime, n, mid)) {
                    result = mid
                    right = mid - 1
                } else {
                    left = mid + 1
                }
            }
            return result
        }
    }

    Some details about JavaScript:

    • Division is not integer division. 7 / 2 is 3.5, so Math.floor(time / v) is needed to count whole trips.
    • >> 1 halves an integer. The operator works on 32-bit signed integers, and the sum left + right stays below that limit here: the constraints keep n * min(cabTripTime) at most 200,000,000, so the sum is at most 400,000,000, under 2,147,483,647. For larger ranges, use Math.floor((left + right) / 2) instead.
    • The helper's parameters shadow the outer ones. hasSolution declares its own cabTripTime and n, which hide the method's parameters of the same names inside the arrow function. The call passes the same values, so nothing changes.
    • reduce with an initial value of cabTripTime[0] finds the minimum. It would also work with Math.min(...cabTripTime), but with up to a million elements the spread can exceed the engine's argument limit, so reduce is the safer choice.

    Complexity

    Each call to hasSolution loops over the m cabs, so it takes O(m). The search halves a range of about T = n * min(cabTripTime) values, so it makes O(log T) calls. Finding the minimum costs one more O(m) pass. The total is O(m log T), and the extra space is O(1).

    Edge cases and common mistakes

    • A cab with trip time 0. Then min(cabTripTime) is 0, so right is 0 and the loop never runs: the method returns 0, as the statement requires. This also means hasSolution never divides by zero.
    • A single cab. For n = 4 and [5] the answer is 20. The range is 1 to 20, and only 20 gives 4 trips.
    • More cabs than trips. For n = 1 and [3, 3, 3] the answer is 3, the time of one trip.
    • Returning mid at the first success. A time that works is not necessarily the smallest one. The search must keep going lower, and result stores the best time seen.
    • Forgetting to floor the division. Without Math.floor, the fraction of a trip left unfinished would be counted, and the answer would be too small.
    • Starting left at 0. With positive trip times, 0 minutes gives no trips, so the lowest useful time is 1.

    Testing

    These inputs give these results: minTime([1, 2], 3) returns 2, minTime([1, 3, 5, 7], 10) returns 7, minTime([5], 4) returns 20, minTime([3, 3, 3], 1) returns 3 and minTime([0, 4], 10) returns 0.

    Keep going with JavaScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free