1870. Minimum Speed to Arrive on Time
Problem Description
You need to commute to the office by taking n trains in sequential order. You have a total of hour hours to complete your journey.
Given:
- A floating-point number
hour: the total time available to reach the office - An integer array
distof lengthn: wheredist[i]represents the distance (in kilometers) of thei-thtrain ride
Key constraints:
- Each train can only depart at an integer hour mark
- You must wait between train rides if needed
- For example, if the first train ride takes 1.5 hours at a certain speed, you must wait an additional 0.5 hours before boarding the second train at the 2-hour mark
The goal is to find the minimum positive integer speed (in kilometers per hour) that all trains must travel at for you to reach the office within the given time limit. If it's impossible to arrive on time, return -1.
The waiting time mechanism works as follows:
- For all trains except the last one, if the travel time is not a whole number, you must wait until the next integer hour to board the next train
- For the last train, no waiting is needed since you've reached your destination
Example of time calculation:
- If you have 3 trains with distances
[1, 3, 2]km and speed is 2 km/h:- First train: takes 0.5 hours, wait 0.5 hours → depart second train at hour 1
- Second train: takes 1.5 hours, wait 0.5 hours → depart third train at hour 3
- Third train: takes 1 hour → arrive at hour 4
- Total time: 4 hours
Additional constraints:
- The answer will not exceed
10^7 hourwill have at most two decimal places
Intuition
The key observation is that speed and time have an inverse relationship - as speed increases, the total time to complete the journey decreases. This monotonic property is crucial for our solution.
Consider what happens at different speeds:
- At a very low speed (say 1 km/h), trains take a long time to cover their distances, resulting in a large total time
- At a higher speed, the same distances are covered faster, reducing the total time
- Once we find a speed that allows us to arrive within
hourhours, any higher speed will also work
This monotonic behavior suggests binary search as an efficient approach. Instead of checking every possible speed from 1 to some maximum value, we can use binary search to quickly narrow down to the minimum speed that works.
Before diving into the search, we need to establish if a solution is even possible. Each train (except possibly the last) requires at least one full hour due to the integer departure constraint. If we have n trains and only hour hours available, where n > ceil(hour), it's mathematically impossible to complete the journey. For instance, with 5 trains and only 4.5 hours, even at infinite speed, we'd need at least 4 hours for the first 4 trains (due to waiting times) plus some time for the last train.
For any given speed v, we can calculate the total time needed:
- For each train except the last: time =
ceil(dist[i] / v)(accounting for waiting time) - For the last train: time =
dist[n-1] / v(no waiting needed)
The search space for speed is bounded:
- Lower bound: 1 km/h (minimum positive integer speed)
- Upper bound:
10^7km/h (given constraint)
We binary search within this range to find the smallest speed where the total calculated time is less than or equal to hour.
Learn more about Binary Search patterns.
Solution Implementation
1from typing import List
2from math import ceil
3
4
5class Solution:
6 def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
7 """
8 Find the minimum speed needed to reach the destination within the given time.
9
10 Args:
11 dist: List of distances for each train segment
12 hour: Maximum time allowed to complete the journey
13
14 Returns:
15 Minimum speed required, or -1 if impossible
16 """
17
18 def can_reach_in_time(speed: int) -> bool:
19 """
20 Check if we can complete the journey within the time limit at given speed.
21
22 For all trains except the last, we must wait for the next integer hour.
23 For the last train, we can arrive at any fractional time.
24 """
25 total_time = 0
26 n = len(dist)
27
28 for index, distance in enumerate(dist):
29 travel_time = distance / speed
30
31 if index == n - 1:
32 total_time += travel_time
33 else:
34 total_time += ceil(travel_time)
35
36 return total_time <= hour
37
38 # If we have more trains than available hours (rounded up), it's impossible
39 if len(dist) > ceil(hour):
40 return -1
41
42 # Binary search using the standard template
43 left, right = 1, 10**7
44 first_true_index = -1
45
46 while left <= right:
47 mid = (left + right) // 2
48 if can_reach_in_time(mid): # feasible condition
49 first_true_index = mid
50 right = mid - 1
51 else:
52 left = mid + 1
53
54 return first_true_index
551class Solution {
2 /**
3 * Finds the minimum speed required to reach the destination within the given time limit.
4 * Uses binary search to find the optimal speed.
5 *
6 * @param dist Array of distances for each segment of the journey
7 * @param hour Maximum time allowed to complete the journey
8 * @return Minimum speed required, or -1 if impossible
9 */
10 public int minSpeedOnTime(int[] dist, double hour) {
11 // Check if it's impossible to complete the journey
12 if (dist.length > Math.ceil(hour)) {
13 return -1;
14 }
15
16 // Binary search using the standard template
17 final int MAX_SPEED = (int) 1e7;
18 int left = 1;
19 int right = MAX_SPEED;
20 int firstTrueIndex = -1;
21
22 while (left <= right) {
23 int mid = left + (right - left) / 2;
24
25 if (canReachOnTime(dist, mid, hour)) { // feasible condition
26 firstTrueIndex = mid;
27 right = mid - 1;
28 } else {
29 left = mid + 1;
30 }
31 }
32
33 return firstTrueIndex;
34 }
35
36 /**
37 * Checks if it's possible to complete the journey within the time limit at given speed.
38 *
39 * @param dist Array of distances for each segment
40 * @param speed Speed to test
41 * @param hour Maximum time allowed
42 * @return true if journey can be completed within time limit, false otherwise
43 */
44 private boolean canReachOnTime(int[] dist, int speed, double hour) {
45 double totalTime = 0;
46 int n = dist.length;
47
48 for (int i = 0; i < n; ++i) {
49 double timeForSegment = (double) dist[i] / speed;
50
51 if (i == n - 1) {
52 totalTime += timeForSegment;
53 } else {
54 totalTime += Math.ceil(timeForSegment);
55 }
56 }
57
58 return totalTime <= hour;
59 }
60}
611class Solution {
2public:
3 int minSpeedOnTime(vector<int>& dist, double hour) {
4 // If number of trains is greater than ceiling of hour, impossible to arrive on time
5 if (dist.size() > ceil(hour)) {
6 return -1;
7 }
8
9 const int MAX_SPEED = 1e7;
10 int n = dist.size();
11
12 // Lambda function to check if given speed allows arrival within time limit
13 auto canArriveOnTime = [&](int speed) {
14 double totalTime = 0;
15
16 for (int i = 0; i < n; ++i) {
17 double travelTime = static_cast<double>(dist[i]) / speed;
18
19 if (i == n - 1) {
20 totalTime += travelTime;
21 } else {
22 totalTime += ceil(travelTime);
23 }
24 }
25
26 return totalTime <= hour;
27 };
28
29 // Binary search using the standard template
30 int left = 1;
31 int right = MAX_SPEED;
32 int firstTrueIndex = -1;
33
34 while (left <= right) {
35 int mid = left + (right - left) / 2;
36
37 if (canArriveOnTime(mid)) { // feasible condition
38 firstTrueIndex = mid;
39 right = mid - 1;
40 } else {
41 left = mid + 1;
42 }
43 }
44
45 return firstTrueIndex;
46 }
47};
481/**
2 * Finds the minimum speed needed to arrive on time given distances and time limit
3 * @param dist - Array of distances for each train segment
4 * @param hour - Maximum time allowed for the entire journey
5 * @returns Minimum speed required, or -1 if impossible
6 */
7function minSpeedOnTime(dist: number[], hour: number): number {
8 // If we have more trains than available hours (rounded up), it's impossible
9 if (dist.length > Math.ceil(hour)) {
10 return -1;
11 }
12
13 const n: number = dist.length;
14 const MAX_SPEED: number = 10 ** 7;
15
16 /**
17 * Checks if a given speed allows completing the journey within time limit
18 * @param speed - The speed to test
19 * @returns true if journey can be completed within hour limit at this speed
20 */
21 const canArriveOnTime = (speed: number): boolean => {
22 let totalTime: number = 0;
23
24 for (let i = 0; i < n; ++i) {
25 const timeForSegment: number = dist[i] / speed;
26
27 if (i === n - 1) {
28 totalTime += timeForSegment;
29 } else {
30 totalTime += Math.ceil(timeForSegment);
31 }
32 }
33
34 return totalTime <= hour;
35 };
36
37 // Binary search using the standard template
38 let left: number = 1;
39 let right: number = MAX_SPEED;
40 let firstTrueIndex: number = -1;
41
42 while (left <= right) {
43 const mid: number = Math.floor((left + right) / 2);
44
45 if (canArriveOnTime(mid)) { // feasible condition
46 firstTrueIndex = mid;
47 right = mid - 1;
48 } else {
49 left = mid + 1;
50 }
51 }
52
53 return firstTrueIndex;
54}
55Solution Approach
The implementation uses binary search to find the minimum speed required. Let's walk through the key components:
Initial Feasibility Check:
First, we check if it's possible to reach on time. If the number of trains len(dist) is greater than ceil(hour), we immediately return -1. This is because even at infinite speed, we'd need at least n-1 full hours for the first n-1 trains due to the waiting constraint.
Speed Validation Function:
The can_reach_in_time(speed) function determines if a given speed allows us to arrive within the time limit:
def can_reach_in_time(speed: int) -> bool:
total_time = 0
for i, d in enumerate(dist):
travel_time = d / speed
if i == len(dist) - 1:
total_time += travel_time
else:
total_time += ceil(travel_time)
return total_time <= hour
For each train ride:
- Calculate travel time:
travel_time = d / speed - If it's the last train, add the exact time
- Otherwise, add
ceil(travel_time)to account for waiting until the next integer hour - Return
Trueif total time is within the limit
Binary Search Using the Standard Template: The feasibility condition creates a pattern as speed increases:
false, false, false, true, true, true, ...
We use the template to find the first true index (minimum speed that works):
left, right = 1, 10**7 first_true_index = -1 while left <= right: mid = (left + right) // 2 if can_reach_in_time(mid): # feasible condition first_true_index = mid right = mid - 1 else: left = mid + 1
Result Handling:
- If
first_true_index == -1, no valid speed exists within our constraints, return-1 - Otherwise, return
first_true_indexas the minimum required speed
The binary search efficiently narrows down from a potential range of 10^7 values to find the exact minimum speed in O(log(10^7)) iterations, with each iteration taking O(n) time to validate the speed.
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 a concrete example to illustrate the solution approach.
Example: dist = [1, 3, 2], hour = 2.5
Step 1: Initial Feasibility Check
- Number of trains:
n = 3 ceil(2.5) = 3- Since
3 ≤ 3, it's potentially possible to complete the journey
Step 2: Binary Search Setup
- Left boundary:
l = 1km/h - Right boundary:
r = 10^7 + 1km/h - We'll search for the minimum speed where we can complete the journey in ≤ 2.5 hours
Step 3: Binary Search Process
Let's trace through a few iterations:
First iteration: mid = (1 + 10^7) / 2 ≈ 5,000,000 km/h
- Train 1:
1/5,000,000hours ≈ 0, wait until hour 1 - Train 2:
3/5,000,000hours ≈ 0, wait until hour 2 - Train 3:
2/5,000,000hours ≈ 0 - Total time ≈ 2 hours < 2.5 ✓
- This speed works, search lower half
Several iterations later: mid = 3 km/h
- Train 1:
1/3 = 0.33hours →ceil(0.33) = 1hour (wait until hour 1) - Train 2:
3/3 = 1hour →ceil(1) = 1hour (depart at hour 2) - Train 3:
2/3 = 0.67hours (no waiting for last train) - Total time =
1 + 1 + 0.67 = 2.67hours > 2.5 ✗ - This speed doesn't work, search higher speeds
Next iteration: mid = 4 km/h
- Train 1:
1/4 = 0.25hours →ceil(0.25) = 1hour - Train 2:
3/4 = 0.75hours →ceil(0.75) = 1hour - Train 3:
2/4 = 0.5hours - Total time =
1 + 1 + 0.5 = 2.5hours = 2.5 ✓ - This speed works exactly!
Continue searching: Check if speed 3 works (it doesn't, as shown above)
Step 4: Result The binary search converges to find that the minimum speed is 4 km/h.
At this speed:
- We board train 1 at hour 0, arrive at 0.25, wait until hour 1
- We board train 2 at hour 1, arrive at 1.75, wait until hour 2
- We board train 3 at hour 2, arrive at 2.5
- Total journey time: exactly 2.5 hours
This demonstrates how the binary search efficiently finds the minimum speed by testing different values and narrowing the search range based on whether each speed allows us to complete the journey on time.
Time and Space Complexity
The time complexity is O(n × log M), where n is the length of the dist array (number of train trips) and M is the upper bound of the speed (10^7 + 1 in this case).
This complexity arises from:
- The binary search performed by
bisect_leftover the range[1, r)wherer = 10^7 + 1, which takesO(log M)iterations - For each iteration of the binary search, the
checkfunction is called, which iterates through allnelements in thedistarray, takingO(n)time - Therefore, the total time complexity is
O(n × log M)
The space complexity is O(1) as the algorithm only uses a constant amount of extra space for variables like s, t, ans, and r, regardless of the input size. The check function and binary search operation do not require any additional space that scales with the input.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using the wrong binary search template variant
A common mistake is using while left < right with right = mid instead of the standard template.
Wrong approach:
while left < right: mid = (left + right) // 2 if can_reach_in_time(mid): right = mid else: left = mid + 1 return left
Correct approach (using the standard template):
first_true_index = -1 while left <= right: mid = (left + right) // 2 if can_reach_in_time(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
2. Incorrect Handling of the Last Train's Travel Time
Pitfall: A common mistake is applying the ceiling function to ALL train travel times, including the last one.
# INCORRECT implementation
def can_reach_in_time(speed: int) -> bool:
total = 0
for d in dist:
total += ceil(d / speed) # Wrong: applies ceiling to all trains
return total <= hour
Why it's wrong: The last train doesn't require waiting since you've reached your destination. If the last train takes 0.5 hours, the total time should include exactly 0.5 hours, not 1 hour.
Solution: Always check if you're on the last train and handle it differently:
def can_reach_in_time(speed: int) -> bool:
total = 0
for i, d in enumerate(dist):
t = d / speed
total += t if i == len(dist) - 1 else ceil(t)
return total <= hour
3. Integer Division Instead of Float Division
Pitfall: Using integer division (//) when calculating travel time, which loses precision:
# INCORRECT - loses decimal precision travel_time = distance // speed # Wrong: integer division
Why it's wrong: If distance is 3 and speed is 2, integer division gives 1 instead of 1.5.
Solution: Always use float division (/) to maintain precision:
travel_time = distance / speed # Correct: float division
4. Not Checking Early Impossibility Cases
Pitfall: Performing binary search without first checking if the problem is solvable:
# INCORRECT - missing feasibility check
def minSpeedOnTime(self, dist: List[int], hour: float) -> int:
# Directly starts binary search without checking if solution exists
# ...
Why it's wrong: If you have 5 trains but only 3.5 hours, it's impossible to complete the journey regardless of speed, since you need at least 4 hours (waiting for integer hours for the first 4 trains).
Solution: Check if len(dist) > ceil(hour) before starting the search:
if len(dist) > ceil(hour):
return -1
How many times is a tree node visited in a depth first search?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!