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.
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 Approach
The solution implements binary search to find the minimum time required. Let's walk through the implementation step by step:
1. Setting up the search range:
- Left boundary:
l = 1
(minimum possible time) - Right boundary:
r = min(time) * totalTrips
(maximum time needed if only the fastest bus operates)
The right boundary is calculated as min(time) * totalTrips
because in the worst case, if only the fastest bus (with minimum trip time) were working alone, it would need this much time to complete all trips.
2. Binary search process:
While l < r
:
- Calculate the middle point:
mid = (l + r) // 2
- Count total trips possible in
mid
time:trips = sum(mid // t for t in time)
- For each bus with trip time
t
, it can completemid // t
trips inmid
time - Sum all these trips to get the total
- For each bus with trip time
- If
trips >= totalTrips
:- We can complete the required trips in
mid
time - Try to find a smaller time:
r = mid
- We can complete the required trips in
- Otherwise:
- We need more time
- Search in the upper half:
l = mid + 1
3. Return the result:
When the loop ends, l
will be the minimum time required.
Implementation using Python's bisect:
The provided solution uses Python's bisect_left
function cleverly:
bisect_left(range(mx), totalTrips, key=lambda x: sum(x // v for v in time))
This searches in the range [0, mx)
for the leftmost position where the key function returns a value >= totalTrips
. The key function lambda x: sum(x // v for v in time)
calculates how many trips can be completed in time x
.
Time Complexity: O(n * log(min(time) * totalTrips))
where n
is the length of the time array. The log factor comes from binary search, and we spend O(n)
time in each iteration to calculate the total trips.
Space Complexity: O(1)
as we only use a constant amount of extra space.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example:
time = [2, 3, 5]
(bus trip times)totalTrips = 8
(required trips)
Step 1: Initialize search boundaries
- Left boundary:
l = 1
(minimum possible time) - Right boundary:
r = min(2, 3, 5) * 8 = 2 * 8 = 16
Step 2: Binary search iterations
Iteration 1:
mid = (1 + 16) // 2 = 8
- Calculate trips at time 8:
- Bus 1:
8 // 2 = 4
trips - Bus 2:
8 // 3 = 2
trips - Bus 3:
8 // 5 = 1
trip - Total:
4 + 2 + 1 = 7
trips
- Bus 1:
- Since
7 < 8
, we need more time - Update:
l = 9
,r = 16
Iteration 2:
mid = (9 + 16) // 2 = 12
- Calculate trips at time 12:
- Bus 1:
12 // 2 = 6
trips - Bus 2:
12 // 3 = 4
trips - Bus 3:
12 // 5 = 2
trips - Total:
6 + 4 + 2 = 12
trips
- Bus 1:
- Since
12 >= 8
, we might be able to do it faster - Update:
l = 9
,r = 12
Iteration 3:
mid = (9 + 12) // 2 = 10
- Calculate trips at time 10:
- Bus 1:
10 // 2 = 5
trips - Bus 2:
10 // 3 = 3
trips - Bus 3:
10 // 5 = 2
trips - Total:
5 + 3 + 2 = 10
trips
- Bus 1:
- Since
10 >= 8
, we might be able to do it faster - Update:
l = 9
,r = 10
Iteration 4:
mid = (9 + 10) // 2 = 9
- Calculate trips at time 9:
- Bus 1:
9 // 2 = 4
trips - Bus 2:
9 // 3 = 3
trips - Bus 3:
9 // 5 = 1
trip - Total:
4 + 3 + 1 = 8
trips
- Bus 1:
- Since
8 >= 8
, we've met our requirement! - Update:
l = 9
,r = 9
Step 3: Return result
The loop terminates when l == r = 9
. The minimum time needed is 9 units.
We can verify: At time 9, the buses complete exactly 8 trips total, meeting our requirement. At time 8, they only complete 7 trips, which is insufficient.
Solution Implementation
1class Solution:
2 def minimumTime(self, time: List[int], totalTrips: int) -> int:
3 # Calculate the maximum possible time needed
4 # Using minimum bus time * total trips as upper bound
5 max_time = min(time) * totalTrips
6
7 # Binary search for the minimum time needed to complete totalTrips
8 # We search in range [0, max_time) for the first time value
9 # where we can complete at least totalTrips
10 return bisect_left(
11 range(max_time),
12 totalTrips,
13 key=lambda current_time: sum(current_time // bus_time for bus_time in time)
14 )
15```
16
17Wait, let me reconsider this solution. The code uses `bisect_left` but we need to import it. Also, let me think more carefully about the logic:
18
19```python3
20from bisect import bisect_left
21from typing import List
22
23class Solution:
24 def minimumTime(self, time: List[int], totalTrips: int) -> int:
25 # Calculate upper bound for binary search
26 # At minimum, we need min(time) * totalTrips time if only fastest bus works
27 max_possible_time = min(time) * totalTrips
28
29 # Use binary search to find minimum time where we can complete totalTrips
30 # bisect_left finds the leftmost position where totalTrips can be inserted
31 # key function calculates how many trips can be completed in 'x' time
32 return bisect_left(
33 range(max_possible_time),
34 totalTrips,
35 key=lambda x: sum(x // bus_time for bus_time in time)
36 )
37
1class Solution {
2 /**
3 * Finds the minimum time required to complete totalTrips trips
4 * using buses with given time intervals.
5 *
6 * @param time Array where time[i] represents the time needed for bus i to complete one trip
7 * @param totalTrips The total number of trips that need to be completed
8 * @return The minimum time required to complete all trips
9 */
10 public long minimumTime(int[] time, int totalTrips) {
11 // Find the minimum time among all buses
12 int minTime = time[0];
13 for (int busTime : time) {
14 minTime = Math.min(minTime, busTime);
15 }
16
17 // Binary search boundaries
18 // Left: minimum possible time (at least 1 unit of time needed)
19 // Right: worst case - all trips done by fastest bus
20 long left = 1;
21 long right = (long) minTime * totalTrips;
22
23 // Binary search to find minimum time
24 while (left < right) {
25 // Calculate middle point
26 long mid = (left + right) >> 1; // Equivalent to (left + right) / 2
27
28 // Count total trips possible within 'mid' time
29 long totalTripsCompleted = 0;
30 for (int busTime : time) {
31 // Each bus can complete mid/busTime trips in 'mid' time
32 totalTripsCompleted += mid / busTime;
33 }
34
35 // Check if we can complete required trips within 'mid' time
36 if (totalTripsCompleted >= totalTrips) {
37 // Can complete trips, try to minimize time further
38 right = mid;
39 } else {
40 // Cannot complete trips, need more time
41 left = mid + 1;
42 }
43 }
44
45 return left;
46 }
47}
48
1class Solution {
2public:
3 long long minimumTime(vector<int>& time, int totalTrips) {
4 // Find the minimum time among all buses
5 int minTime = *min_element(time.begin(), time.end());
6
7 // Binary search boundaries
8 // Left: minimum possible time is 1
9 // Right: maximum possible time is when the fastest bus completes all trips
10 long long left = 1;
11 long long right = static_cast<long long>(minTime) * totalTrips;
12
13 // Binary search to find the minimum time needed
14 while (left < right) {
15 // Calculate middle point
16 long long mid = (left + right) / 2;
17
18 // Count total trips that can be completed in 'mid' time
19 long long totalCompletedTrips = 0;
20 for (int busTime : time) {
21 totalCompletedTrips += mid / busTime;
22 }
23
24 // Check if we can complete required trips in 'mid' time
25 if (totalCompletedTrips >= totalTrips) {
26 // Can complete all trips, try to find a smaller time
27 right = mid;
28 } else {
29 // Cannot complete all trips, need more time
30 left = mid + 1;
31 }
32 }
33
34 // Return the minimum time needed to complete all trips
35 return left;
36 }
37};
38
1/**
2 * Finds the minimum time required for all buses to complete at least totalTrips trips
3 * Uses binary search to find the optimal time
4 *
5 * @param time - Array where time[i] represents the time the i-th bus takes to complete one trip
6 * @param totalTrips - The total number of trips all buses should complete
7 * @returns The minimum time required for all buses to complete totalTrips
8 */
9function minimumTime(time: number[], totalTrips: number): number {
10 // Initialize binary search boundaries
11 // Left boundary: minimum possible time is 1
12 let left: bigint = 1n;
13
14 // Right boundary: worst case is the fastest bus doing all trips alone
15 let right: bigint = BigInt(Math.min(...time)) * BigInt(totalTrips);
16
17 // Binary search to find the minimum time
18 while (left < right) {
19 // Calculate middle point using bit shift for division by 2
20 const mid: bigint = (left + right) >> 1n;
21
22 // Count total trips all buses can complete in 'mid' time
23 // For each bus, the number of trips = mid / time[i]
24 const totalCompletedTrips: bigint = time.reduce(
25 (accumulator: bigint, busTime: number) => accumulator + mid / BigInt(busTime),
26 0n
27 );
28
29 // Check if we can complete required trips in 'mid' time
30 if (totalCompletedTrips >= BigInt(totalTrips)) {
31 // Can complete required trips, try to find a smaller time
32 right = mid;
33 } else {
34 // Cannot complete required trips, need more time
35 left = mid + 1n;
36 }
37 }
38
39 // Convert result back to number and return
40 return Number(left);
41}
42
Time and Space Complexity
The time complexity is O(n × log(m × k))
, where:
n
is the length of the arraytime
(used in the sum computation within the key function)m
is the minimum value in the arraytime
(found bymin(time)
)k
istotalTrips
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. Off-by-One Error in Binary Search Range
The solution uses range(max_possible_time)
which creates a range from [0, max_possible_time)
. This has two issues:
- It starts from 0, but time 0 would result in 0 trips (division by bus times gives 0)
- It excludes
max_possible_time
itself, which might be the actual answer
Why this is problematic: If the minimum time needed is exactly min(time) * totalTrips
, the search will fail to find it since it's excluded from the range.
Solution:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
max_possible_time = min(time) * totalTrips
# Include max_possible_time and start from 1
return bisect_left(
range(1, max_possible_time + 1),
totalTrips,
key=lambda x: sum(x // bus_time for bus_time in time)
)
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 potentially overflow in some languages (though Python handles arbitrary precision).
Solution: Use a more conservative upper bound or implement manual binary search:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
left, right = 1, min(time) * totalTrips
while left < right:
mid = left + (right - left) // 2
trips_completed = sum(mid // bus_time for bus_time in time)
if trips_completed >= totalTrips:
right = mid
else:
left = mid + 1
return left
3. Misunderstanding bisect_left Behavior
bisect_left
with a key function doesn't work as expected in older Python versions (before 3.10). The key parameter was only added in Python 3.10.
Solution for compatibility: Implement binary search manually or use a wrapper:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
def can_complete_trips(target_time):
return sum(target_time // bus_time for bus_time in time) >= totalTrips
left, right = 1, min(time) * totalTrips
while left < right:
mid = (left + right) // 2
if can_complete_trips(mid):
right = mid
else:
left = mid + 1
return left
4. Edge Case: Single Bus
When there's only one bus, the calculation becomes straightforward but the binary search adds unnecessary complexity.
Optimization:
def minimumTime(self, time: List[int], totalTrips: int) -> int:
if len(time) == 1:
return time[0] * totalTrips
# Continue with binary search for multiple buses
...
Which technique can we use to find the middle of a linked list?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!