Facebook Pixel

2187. Minimum Time to Complete Trips

Problem Description

You have an array time where time[i] represents how long it takes the i-th bus to complete one trip.

Each bus can make multiple trips one after another - as soon as a bus finishes one trip, it can immediately start the next trip. All buses operate independently, meaning they don't affect each other's schedules.

Given an integer totalTrips, which represents the total number of trips that need to be completed by all buses combined, you need to find the minimum amount of time required for all buses together to complete at least totalTrips trips.

For example, if you have buses with trip times [1, 2, 3] and need totalTrips = 5 trips total:

  • In 3 units of time: Bus 1 completes 3 trips, Bus 2 completes 1 trip, Bus 3 completes 1 trip = 5 trips total
  • So the minimum time would be 3

The task is to determine the shortest time period in which the combined effort of all buses can achieve the required number of trips.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that this problem has a monotonic property: if we can complete at least totalTrips in time t, then we can definitely complete at least totalTrips in any time greater than t. This monotonic nature immediately suggests binary search as a potential approach.

Think about it this way - given a specific amount of time t, we can calculate exactly how many trips each bus can complete: t // time[i] for bus i. The total number of trips completed would be the sum of all individual bus trips: sum(t // time[i] for all i).

Now we need to find the minimum time where this sum is at least totalTrips. We could check every possible time value starting from 1, but that would be inefficient. Instead, we can use binary search to quickly narrow down the answer.

For the search boundaries:

  • The minimum possible time is 1 (we need at least 1 unit of time to complete any trip)
  • The maximum time we'd ever need is when only the fastest bus is working to complete all trips: min(time) * totalTrips

Within this range, we binary search for the smallest time value where the total number of completed trips meets or exceeds totalTrips. The beauty of this approach is that we're essentially converting the problem from "find the minimum time" to "check if a given time is sufficient", which is much easier to compute.

The solution cleverly uses bisect_left with a key function that calculates the number of trips possible at each time point, searching for when we first reach totalTrips completed trips.

Learn more about Binary Search patterns.

Solution Implementation

1class Solution:
2    def minimumTime(self, time: List[int], totalTrips: int) -> int:
3        # Define feasible function: can we complete totalTrips in given time?
4        def feasible(current_time: int) -> bool:
5            total_trips = sum(current_time // bus_time for bus_time in time)
6            return total_trips >= totalTrips
7
8        # Binary search setup
9        left = 1  # Minimum possible time
10        right = min(time) * totalTrips  # Maximum time if only fastest bus works
11        first_true_index = -1
12
13        # Binary search for the first feasible time
14        while left <= right:
15            mid = (left + right) // 2
16            if feasible(mid):
17                first_true_index = mid  # Found a valid answer
18                right = mid - 1  # Try to find smaller time
19            else:
20                left = mid + 1  # Need more time
21
22        return first_true_index
23
1class Solution {
2    public long minimumTime(int[] time, int totalTrips) {
3        // Find the minimum bus time for upper bound calculation
4        int minBusTime = time[0];
5        for (int busTime : time) {
6            minBusTime = Math.min(minBusTime, busTime);
7        }
8
9        // Binary search setup
10        long left = 1;  // Minimum possible time
11        long right = (long) minBusTime * totalTrips;  // Maximum time needed
12        long firstTrueIndex = -1;
13
14        // Binary search for the first feasible time
15        while (left <= right) {
16            long mid = left + (right - left) / 2;
17
18            // Check if we can complete totalTrips in mid time
19            if (feasible(time, mid, totalTrips)) {
20                firstTrueIndex = mid;  // Found a valid answer
21                right = mid - 1;  // Try to find smaller time
22            } else {
23                left = mid + 1;  // Need more time
24            }
25        }
26
27        return firstTrueIndex;
28    }
29
30    // Feasible function: can we complete totalTrips in given time?
31    private boolean feasible(int[] time, long currentTime, int totalTrips) {
32        long tripsCompleted = 0;
33        for (int busTime : time) {
34            tripsCompleted += currentTime / busTime;
35        }
36        return tripsCompleted >= totalTrips;
37    }
38}
39
1class Solution {
2public:
3    long long minimumTime(vector<int>& time, int totalTrips) {
4        // Find the minimum bus time for upper bound calculation
5        int minBusTime = *min_element(time.begin(), time.end());
6
7        // Binary search setup
8        long long left = 1;  // Minimum possible time
9        long long right = static_cast<long long>(minBusTime) * totalTrips;
10        long long firstTrueIndex = -1;
11
12        // Feasible function: can we complete totalTrips in given time?
13        auto feasible = [&](long long currentTime) -> bool {
14            long long tripsCompleted = 0;
15            for (int busTime : time) {
16                tripsCompleted += currentTime / busTime;
17            }
18            return tripsCompleted >= totalTrips;
19        };
20
21        // Binary search for the first feasible time
22        while (left <= right) {
23            long long mid = left + (right - left) / 2;
24
25            if (feasible(mid)) {
26                firstTrueIndex = mid;  // Found a valid answer
27                right = mid - 1;  // Try to find smaller time
28            } else {
29                left = mid + 1;  // Need more time
30            }
31        }
32
33        return firstTrueIndex;
34    }
35};
36
1function minimumTime(time: number[], totalTrips: number): number {
2    // Feasible function: can we complete totalTrips in given time?
3    const feasible = (currentTime: bigint): boolean => {
4        let tripsCompleted: bigint = 0n;
5        for (const busTime of time) {
6            tripsCompleted += currentTime / BigInt(busTime);
7        }
8        return tripsCompleted >= BigInt(totalTrips);
9    };
10
11    // Binary search setup
12    let left: bigint = 1n;  // Minimum possible time
13    let right: bigint = BigInt(Math.min(...time)) * BigInt(totalTrips);
14    let firstTrueIndex: bigint = -1n;
15
16    // Binary search for the first feasible time
17    while (left <= right) {
18        const mid: bigint = (left + right) / 2n;
19
20        if (feasible(mid)) {
21            firstTrueIndex = mid;  // Found a valid answer
22            right = mid - 1n;  // Try to find smaller time
23        } else {
24            left = mid + 1n;  // Need more time
25        }
26    }
27
28    return Number(firstTrueIndex);
29}
30

Solution Approach

The solution uses the binary search template to find the minimum time required. We're looking for the first time value where we can complete at least totalTrips - a classic first_true_index pattern.

Defining the Feasible Function:

A time value t is feasible if we can complete at least totalTrips in time t:

feasible(t) = sum(t // busTime for busTime in time) >= totalTrips

This creates the pattern: false, false, ..., true, true, true We want to find the first true (minimum feasible time).

Setting Up the Search Range:

  • left = 1: Minimum possible time (at least 1 unit needed for any trip)
  • right = min(time) * totalTrips: Maximum time needed if only the fastest bus operates

Binary Search Template:

left, right = 1, min(time) * totalTrips
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):  # Can complete totalTrips in mid time
        first_true_index = mid
        right = mid - 1  # Try to find smaller time
    else:
        left = mid + 1  # Need more time

return first_true_index

Why This Works:

  • When feasible(mid) is true, we've found a valid answer but want to minimize, so we search left (right = mid - 1)
  • When feasible(mid) is false, we need more time, so we search right (left = mid + 1)
  • first_true_index always holds the smallest feasible time found so far

Time Complexity: O(n × log(min(time) × totalTrips)) where n is the number of buses.

Space Complexity: O(1) - only constant extra space used.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through with time = [2, 3, 5] and totalTrips = 8.

Initialize:

  • left = 1, right = min(2, 3, 5) × 8 = 16
  • first_true_index = -1

Iteration 1: left = 1, right = 16

  • mid = (1 + 16) // 2 = 8
  • Trips at time 8: 8//2 + 8//3 + 8//5 = 4 + 2 + 1 = 7
  • feasible(8) = (7 >= 8)false
  • left = 8 + 1 = 9

Iteration 2: left = 9, right = 16

  • mid = (9 + 16) // 2 = 12
  • Trips at time 12: 12//2 + 12//3 + 12//5 = 6 + 4 + 2 = 12
  • feasible(12) = (12 >= 8)true
  • first_true_index = 12, right = 12 - 1 = 11

Iteration 3: left = 9, right = 11

  • mid = (9 + 11) // 2 = 10
  • Trips at time 10: 10//2 + 10//3 + 10//5 = 5 + 3 + 2 = 10
  • feasible(10) = (10 >= 8)true
  • first_true_index = 10, right = 10 - 1 = 9

Iteration 4: left = 9, right = 9

  • mid = (9 + 9) // 2 = 9
  • Trips at time 9: 9//2 + 9//3 + 9//5 = 4 + 3 + 1 = 8
  • feasible(9) = (8 >= 8)true
  • first_true_index = 9, right = 9 - 1 = 8

Termination: left = 9 > right = 8, exit loop

Result: first_true_index = 9

Verification: At time 9, buses complete exactly 8 trips. At time 8, only 7 trips - insufficient.

Time and Space Complexity

The time complexity is O(n × log(m × k)), where:

  • n is the length of the array time (used in the sum computation within the key function)
  • m is the minimum value in the array time (found by min(time))
  • k is totalTrips

The binary search operates on a range from 0 to mx = min(time) × totalTrips, which has O(m × k) elements. Each binary search iteration calls the key function lambda x: sum(x // v for v in time), which takes O(n) time to compute the sum over all buses. Since binary search performs O(log(m × k)) iterations, the total time complexity is O(n × log(m × k)).

The space complexity is O(1) as the algorithm only uses a constant amount of extra space for variables. The bisect_left function with a range object and key function doesn't create the entire range in memory but rather computes values on-the-fly, maintaining constant space usage.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using Wrong Binary Search Template Variant

A common mistake is using the while left < right with right = mid variant instead of the standard template with first_true_index tracking.

Wrong approach:

while left < right:
    mid = (left + right) // 2
    if feasible(mid):
        right = mid  # Wrong: should be right = mid - 1
    else:
        left = mid + 1
return left  # Wrong: should return first_true_index

Correct approach (using template):

first_true_index = -1
while left <= right:
    mid = (left + right) // 2
    if feasible(mid):
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1
return first_true_index

2. Integer Overflow for Large Inputs

When totalTrips is very large (up to 10^7) and min(time) is also large, the multiplication min(time) * totalTrips could overflow in languages with fixed integer sizes.

Solution: Use appropriate data types (long in Java, long long in C++, bigint in TypeScript):

long right = (long) minTime * totalTrips;  // Cast to long before multiplication

3. Forgetting to Handle Edge Cases

When all buses are very slow or totalTrips is 1, the result might be at the boundary.

Solution: The template handles this naturally since first_true_index will be set on the first feasible value found.

4. Off-by-One in Search Range

Starting with left = 0 instead of left = 1 can cause issues since time 0 results in 0 trips.

Solution: Always start with left = 1 as the minimum meaningful time value.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More