Facebook Pixel

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 dist of length n: where dist[i] represents the distance (in kilometers) of the i-th train 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
  • hour will have at most two decimal places
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 hour hours, 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^7 km/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 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.

Binary Search Setup:

  • Left boundary l = 1: minimum possible speed (1 km/h)
  • Right boundary r = 10^7 + 1: maximum possible speed plus one
  • We use bisect_left to find the leftmost position where our condition becomes true

Speed Validation Function: The check(v) function determines if a given speed v allows us to arrive within the time limit:

def check(v: int) -> bool:
    s = 0
    for i, d in enumerate(dist):
        t = d / v
        s += t if i == len(dist) - 1 else ceil(t)
    return s <= hour

For each train ride:

  • Calculate travel time: t = d / v
  • If it's the last train (i == len(dist) - 1), add the exact time t
  • Otherwise, add ceil(t) to account for waiting until the next integer hour
  • Return True if total time s is within the limit

Binary Search Execution: The solution uses Python's bisect_left with a custom key function:

ans = bisect_left(range(1, r), True, key=check) + 1

This finds the smallest speed in the range [1, r) where check(speed) returns True. The +1 adjusts for the 0-based indexing of the range.

Result Handling:

  • If ans == r (equals 10^7 + 1), no valid speed exists within our constraints, return -1
  • Otherwise, return ans as 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 Evaluator

Example 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 = 1 km/h
  • Right boundary: r = 10^7 + 1 km/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,000 hours ≈ 0, wait until hour 1
  • Train 2: 3/5,000,000 hours ≈ 0, wait until hour 2
  • Train 3: 2/5,000,000 hours ≈ 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.33 hours → ceil(0.33) = 1 hour (wait until hour 1)
  • Train 2: 3/3 = 1 hour → ceil(1) = 1 hour (depart at hour 2)
  • Train 3: 2/3 = 0.67 hours (no waiting for last train)
  • Total time = 1 + 1 + 0.67 = 2.67 hours > 2.5 ✗
  • This speed doesn't work, search higher speeds

Next iteration: mid = 4 km/h

  • Train 1: 1/4 = 0.25 hours → ceil(0.25) = 1 hour
  • Train 2: 3/4 = 0.75 hours → ceil(0.75) = 1 hour
  • Train 3: 2/4 = 0.5 hours
  • Total time = 1 + 1 + 0.5 = 2.5 hours = 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.

Solution Implementation

1from typing import List
2from math import ceil
3from bisect import bisect_left
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          
27            for index, distance in enumerate(dist):
28                travel_time = distance / speed
29              
30                # For the last train, we don't need to wait for the next hour
31                if index == len(dist) - 1:
32                    total_time += travel_time
33                else:
34                    # For other trains, round up to next integer hour
35                    total_time += ceil(travel_time)
36          
37            return total_time <= hour
38      
39        # If we have more trains than available hours (rounded up), it's impossible
40        # Since we need at least 1 hour per train (except possibly the last one)
41        if len(dist) > ceil(hour):
42            return -1
43      
44        # Maximum possible speed to search (10^7 based on problem constraints)
45        max_speed = 10**7 + 1
46      
47        # Binary search for the minimum speed that satisfies the time constraint
48        # bisect_left finds the leftmost position where True would be inserted
49        min_speed = bisect_left(range(1, max_speed), True, key=can_reach_in_time) + 1
50      
51        # If no valid speed found within the search range
52        if min_speed == max_speed:
53            return -1
54      
55        return min_speed
56
1class 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        // We need at least (n-1) full hours plus some time for the last segment
13        if (dist.length > Math.ceil(hour)) {
14            return -1;
15        }
16      
17        // Define search boundaries
18        final int MAX_SPEED = (int) 1e7;
19        int left = 1;                    // Minimum possible speed
20        int right = MAX_SPEED + 1;       // Maximum possible speed + 1 (exclusive)
21      
22        // Binary search for the minimum valid speed
23        while (left < right) {
24            int mid = (left + right) >> 1;  // Equivalent to (left + right) / 2
25          
26            if (canReachOnTime(dist, mid, hour)) {
27                // If we can reach with this speed, try a lower speed
28                right = mid;
29            } else {
30                // If we cannot reach with this speed, we need a higher speed
31                left = mid + 1;
32            }
33        }
34      
35        // Return the result, or -1 if no valid speed exists
36        return left > MAX_SPEED ? -1 : left;
37    }
38  
39    /**
40     * Checks if it's possible to complete the journey within the time limit at given speed.
41     * 
42     * @param dist Array of distances for each segment
43     * @param speed Speed to test
44     * @param hour Maximum time allowed
45     * @return true if journey can be completed within time limit, false otherwise
46     */
47    private boolean canReachOnTime(int[] dist, int speed, double hour) {
48        double totalTime = 0;
49        int numSegments = dist.length;
50      
51        // Calculate total time for the journey
52        for (int i = 0; i < numSegments; ++i) {
53            // Calculate time for current segment
54            double timeForSegment = (double) dist[i] / speed;
55          
56            // For all segments except the last, we must wait for the next whole hour
57            // For the last segment, we can arrive at any fractional hour
58            if (i == numSegments - 1) {
59                totalTime += timeForSegment;
60            } else {
61                totalTime += Math.ceil(timeForSegment);
62            }
63        }
64      
65        // Check if total time is within the limit
66        return totalTime <= hour;
67    }
68}
69
1class 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        // (Each train except last takes at least 1 hour, last train takes > 0 time)
6        if (dist.size() > ceil(hour)) {
7            return -1;
8        }
9      
10        // Set search range for binary search
11        const int MAX_SPEED = 1e7;
12        int left = 1;                    // Minimum possible speed
13        int right = MAX_SPEED + 1;        // Maximum possible speed + 1 (exclusive)
14        int numTrains = dist.size();
15      
16        // Lambda function to check if given speed allows arrival within time limit
17        auto canArriveOnTime = [&](int speed) {
18            double totalTime = 0;
19          
20            // Calculate total travel time
21            for (int i = 0; i < numTrains; ++i) {
22                double travelTime = static_cast<double>(dist[i]) / speed;
23              
24                // For all trains except the last, must wait for next integer hour
25                // For the last train, can arrive at any fractional time
26                if (i == numTrains - 1) {
27                    totalTime += travelTime;
28                } else {
29                    totalTime += ceil(travelTime);
30                }
31            }
32          
33            return totalTime <= hour;
34        };
35      
36        // Binary search for minimum speed
37        while (left < right) {
38            int mid = left + (right - left) / 2;  // Avoid potential overflow
39          
40            if (canArriveOnTime(mid)) {
41                // If current speed works, try to find a smaller speed
42                right = mid;
43            } else {
44                // If current speed doesn't work, need higher speed
45                left = mid + 1;
46            }
47        }
48      
49        // Check if the result exceeds maximum allowed speed
50        return left > MAX_SPEED ? -1 : left;
51    }
52};
53
1/**
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    // Each train except the last takes at least 1 hour (we must wait for the next hour)
10    if (dist.length > Math.ceil(hour)) {
11        return -1;
12    }
13  
14    const trainCount: number = dist.length;
15    const maxSpeed: number = 10 ** 7; // Maximum possible speed constraint
16  
17    /**
18     * Checks if a given speed allows completing the journey within time limit
19     * @param speed - The speed to test
20     * @returns true if journey can be completed within hour limit at this speed
21     */
22    const canArriveOnTime = (speed: number): boolean => {
23        let totalTime: number = 0;
24      
25        for (let i = 0; i < trainCount; ++i) {
26            const timeForSegment: number = dist[i] / speed;
27          
28            // For all trains except the last, we must wait until the next hour
29            // For the last train, we can arrive at any fractional hour
30            if (i === trainCount - 1) {
31                totalTime += timeForSegment;
32            } else {
33                totalTime += Math.ceil(timeForSegment);
34            }
35        }
36      
37        return totalTime <= hour;
38    };
39  
40    // Binary search for the minimum speed
41    let leftBound: number = 1;
42    let rightBound: number = maxSpeed + 1;
43  
44    while (leftBound < rightBound) {
45        const midSpeed: number = (leftBound + rightBound) >> 1; // Equivalent to Math.floor((left + right) / 2)
46      
47        if (canArriveOnTime(midSpeed)) {
48            // If we can arrive on time at this speed, try slower speeds
49            rightBound = midSpeed;
50        } else {
51            // If we cannot arrive on time, we need faster speeds
52            leftBound = midSpeed + 1;
53        }
54    }
55  
56    // If the minimum speed exceeds the maximum allowed, return -1
57    return leftBound > maxSpeed ? -1 : leftBound;
58}
59

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_left over the range [1, r) where r = 10^7 + 1, which takes O(log M) iterations
  • For each iteration of the binary search, the check function is called, which iterates through all n elements in the dist array, taking O(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. 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. This would incorrectly round up the last train's travel time to the next integer hour.

# INCORRECT implementation
def check(v: int) -> bool:
    s = 0
    for d in dist:
        s += ceil(d / v)  # Wrong: applies ceiling to all trains
    return s <= 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 check(v: int) -> bool:
    s = 0
    for i, d in enumerate(dist):
        t = d / v
        s += t if i == len(dist) - 1 else ceil(t)
    return s <= hour

2. 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. This would underestimate the actual travel time.

Solution: Always use float division (/) to maintain precision:

travel_time = distance / speed  # Correct: float division

3. Off-by-One Error in Binary Search Boundaries

Pitfall: Setting incorrect boundaries or forgetting to adjust the result from bisect_left:

# INCORRECT - forgets to add 1
ans = bisect_left(range(1, max_speed), True, key=check)  # Missing +1

Why it's wrong: bisect_left returns an index into the range starting from 0, but our speed range starts from 1. Without adding 1, you'd return speed 0 for the first valid position.

Solution: Remember to add 1 to adjust for the starting position:

ans = bisect_left(range(1, max_speed), True, key=check) + 1

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
    return bisect_left(range(1, 10**7 + 1), True, key=check) + 1

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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which two pointer techniques do you use to check if a string is a palindrome?


Recommended Readings

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

Load More