Facebook Pixel

1883. Minimum Skips to Arrive at Meeting On Time

Problem Description

You need to travel through n roads to reach a meeting within hoursBefore hours. Each road has a specific length given in the array dist, where dist[i] represents the length of the i-th road in kilometers. You travel at a constant speed (in km/h).

Key Rules:

  1. After traveling each road (except the last one), you must rest until the next integer hour before continuing.

    • Example: If traveling a road takes 1.4 hours, you must wait until hour 2 before starting the next road
    • If traveling takes exactly 2 hours, no waiting is needed
  2. You can choose to skip some rest periods to save time and arrive on time.

    • When you skip a rest, you immediately continue to the next road without waiting
    • Example: If road 1 takes 1.4 hours and road 2 takes 0.6 hours, skipping the rest after road 1 means you finish road 2 at exactly 2 hours total
  3. The last road doesn't require rest since you've already arrived at the meeting.

Goal: Find the minimum number of rest periods you need to skip to arrive within hoursBefore hours. Return -1 if it's impossible to arrive on time even by skipping all rests.

The solution uses dynamic programming where f[i][j] represents the minimum time needed to travel the first i roads while skipping exactly j rest periods. For each road, you decide whether to skip its rest period or not, choosing the option that minimizes total travel time.

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

Intuition

The key insight is that we need to find the optimal balance between skipping and not skipping rest periods. Each skip saves us waiting time, but we have a limited number of skips that would be useful.

Think about it this way: if we travel a road in 1.4 hours, we waste 0.6 hours waiting for the next integer hour. By skipping this rest, we save those 0.6 hours. However, not all skips are equally valuable - some save more time than others.

This naturally leads to a dynamic programming approach where we track the minimum time needed for different numbers of skips. We want to explore all possibilities: what's the minimum time if we skip 0 rests? 1 rest? 2 rests? And so on.

For each road we encounter, we face a decision: should we rest after this road or skip the rest? This decision depends on:

  • How much time we've already spent
  • How many skips we've already used
  • Whether skipping here would help us arrive on time

The state f[i][j] captures this perfectly - it tells us the minimum time to complete i roads with exactly j skips. For each new road, we can:

  1. Not skip the rest: We add the road's travel time and round up to the next integer hour (using ceiling function)
  2. Skip the rest: We simply add the road's travel time without rounding

By building up from smaller subproblems (fewer roads, fewer skips) to larger ones (all roads, various skip counts), we can find the minimum number of skips needed. We just check which is the smallest j where f[n][j] ≤ hoursBefore.

The epsilon (eps = 1e-8) handling is a technical detail to avoid floating-point precision issues when using the ceiling function and comparing with hoursBefore.

Learn more about Dynamic Programming patterns.

Solution Approach

We implement a dynamic programming solution where f[i][j] represents the minimum time needed to travel the first i roads while skipping exactly j rest periods.

Initialization:

  • Create a 2D array f of size (n+1) × (n+1) initialized with infinity
  • Set f[0][0] = 0 (no roads traveled, no skips used, zero time)
  • Define eps = 1e-8 to handle floating-point precision issues

State Transitions: For each road i (from 1 to n) and each possible number of skips j (from 0 to i):

  1. Option 1 - Don't skip the rest after road i:

    • Only valid when j < i (we can't skip more rests than roads minus one)
    • Time needed: ceil(f[i-1][j] + dist[i-1]/speed - eps)
    • We subtract eps before ceiling to avoid floating-point errors
    • The ceiling function ensures we wait until the next integer hour
  2. Option 2 - Skip the rest after road i:

    • Only valid when j > 0 (we need at least one skip available)
    • Time needed: f[i-1][j-1] + dist[i-1]/speed
    • No ceiling needed since we're skipping the rest period

The state transition equation becomes:

f[i][j] = min(
    ceil(f[i-1][j] + dist[i-1]/speed - eps),  // don't skip
    f[i-1][j-1] + dist[i-1]/speed              // skip (if j > 0)
)

Finding the Answer: After filling the DP table, we iterate through j from 0 to n:

  • Check if f[n][j] <= hoursBefore + eps
  • Return the first (minimum) j that satisfies this condition
  • If no valid j exists, return -1

The eps addition when comparing with hoursBefore compensates for the earlier subtraction and ensures numerical stability in our floating-point comparisons.

Time Complexity: O(n²) for filling the DP table
Space Complexity: O(n²) for the DP array

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 understand the solution approach.

Input: dist = [2, 3, 2], speed = 2, hoursBefore = 6

This means we have 3 roads of lengths 2km, 3km, and 2km, traveling at 2 km/h, and need to arrive within 6 hours.

Step 1: Calculate travel times for each road

  • Road 1: 2km ÷ 2km/h = 1.0 hours
  • Road 2: 3km ÷ 2km/h = 1.5 hours
  • Road 3: 2km ÷ 2km/h = 1.0 hours

Step 2: Build the DP table

We'll create a table f[i][j] where rows represent roads completed and columns represent skips used.

Initial state: f[0][0] = 0 (no roads, no time)

For Road 1 (i=1):

  • f[1][0]: No skips → travel 1.0 hour, rest until hour 1 → ceil(0 + 1.0) = 1.0
  • f[1][1]: Can't skip (no previous road to skip after)

For Road 2 (i=2):

  • f[2][0]: No skips → from f[1][0]=1.0, add 1.5 hours, rest → ceil(1.0 + 1.5) = 3.0
  • f[2][1]: Skip once
    • Option 1: Don't skip after road 2 → ceil(f[1][1] + 1.5) = ∞ (f[1][1] doesn't exist)
    • Option 2: Skip after road 1 → f[1][0] + 1.5 = 1.0 + 1.5 = 2.5
    • Choose: 2.5
  • f[2][2]: Can't use 2 skips for 2 roads (max 1 skip possible)

For Road 3 (i=3):

  • f[3][0]: No skips → from f[2][0]=3.0, add 1.0 hour (no rest after last road) → 3.0 + 1.0 = 4.0
  • f[3][1]: Skip once
    • Option 1: Don't skip after road 3 (last road, no rest anyway) → f[2][1] + 1.0 = 2.5 + 1.0 = 3.5
    • Option 2: Skip after road 2 → ceil(f[2][0] + 1.0) = ceil(3.0 + 1.0) = 4.0
    • Choose: min(3.5, 4.0) = 3.5
  • f[3][2]: Skip twice
    • From f[2][1]=2.5, skip after road 2 → 2.5 + 1.0 = 3.5
    • (Other option gives ∞)
    • Choose: 3.5

Step 3: Find the answer

Final DP table:

     j=0   j=1   j=2   j=3
i=0  0     ∞     ∞     ∞
i=1  1.0   ∞     ∞     ∞
i=2  3.0   2.5   ∞     ∞
i=3  4.0   3.5   3.5

Check f[3][j] values against hoursBefore = 6:

  • f[3][0] = 4.0 ≤ 6 ✓
  • f[3][1] = 3.5 ≤ 6 ✓
  • f[3][2] = 3.5 ≤ 6 ✓

The minimum j where f[3][j] ≤ 6 is j=0, so we need to skip 0 rest periods to arrive on time.

Verification:

  • Travel road 1: 1.0 hour, rest until hour 1
  • Travel road 2: 1.5 hours (from hour 1 to 2.5), rest until hour 3
  • Travel road 3: 1.0 hour (from hour 3 to 4)
  • Total: 4 hours ≤ 6 hours ✓

Solution Implementation

1class Solution:
2    def minSkips(self, dist: List[int], speed: int, hoursBefore: int) -> int:
3        """
4        Find the minimum number of skips needed to arrive before hoursBefore.
5        A skip means not resting after completing a road segment.
6      
7        Args:
8            dist: List of distances for each road segment
9            speed: Constant travel speed
10            hoursBefore: Maximum allowed total travel time
11          
12        Returns:
13            Minimum number of skips needed, or -1 if impossible
14        """
15        num_segments = len(dist)
16      
17        # dp[i][j] represents minimum time to complete first i segments with j skips
18        # Initialize with infinity to represent impossible states
19        dp = [[float('inf')] * (num_segments + 1) for _ in range(num_segments + 1)]
20      
21        # Base case: 0 segments with 0 skips takes 0 time
22        dp[0][0] = 0
23      
24        # Small epsilon to handle floating point precision issues
25        epsilon = 1e-8
26      
27        # Process each road segment
28        for segment_idx, distance in enumerate(dist, 1):
29            # Try different numbers of skips (from 0 to segment_idx)
30            for num_skips in range(segment_idx + 1):
31              
32                # Option 1: Don't skip at this segment (rest after this segment)
33                # Can only do this if we haven't used all available segments for skips
34                if num_skips < segment_idx:
35                    # Add travel time and round up (rest until next hour)
36                    # Subtract epsilon before ceiling to handle floating point errors
37                    time_with_rest = dp[segment_idx - 1][num_skips] + distance / speed
38                    dp[segment_idx][num_skips] = min(
39                        dp[segment_idx][num_skips], 
40                        ceil(time_with_rest - epsilon)
41                    )
42              
43                # Option 2: Skip at this segment (don't rest after this segment)
44                # Can only do this if we have at least one skip to use
45                if num_skips > 0:
46                    # Add travel time without rounding (no rest)
47                    time_with_skip = dp[segment_idx - 1][num_skips - 1] + distance / speed
48                    dp[segment_idx][num_skips] = min(
49                        dp[segment_idx][num_skips], 
50                        time_with_skip
51                    )
52      
53        # Find minimum number of skips that allows arrival within time limit
54        for skips_used in range(num_segments + 1):
55            # Check if total time is within the allowed limit (with epsilon tolerance)
56            if dp[num_segments][skips_used] <= hoursBefore + epsilon:
57                return skips_used
58      
59        # If no valid solution exists, return -1
60        return -1
61
1class Solution {
2    public int minSkips(int[] dist, int speed, int hoursBefore) {
3        int n = dist.length;
4      
5        // dp[i][j] represents the minimum time to reach the i-th road segment
6        // with exactly j skips (no rest)
7        double[][] dp = new double[n + 1][n + 1];
8      
9        // Initialize all values to infinity
10        for (int i = 0; i <= n; i++) {
11            Arrays.fill(dp[i], Double.MAX_VALUE);
12        }
13      
14        // Base case: starting point with 0 skips takes 0 time
15        dp[0][0] = 0;
16      
17        // Small epsilon value to handle floating point precision issues
18        double epsilon = 1e-8;
19      
20        // Iterate through each road segment
21        for (int i = 1; i <= n; i++) {
22            // Try different number of skips (from 0 to i)
23            for (int j = 0; j <= i; j++) {
24                // Case 1: Don't skip the rest at (i-1)-th segment
25                // We need to wait (ceil the time) before continuing
26                if (j < i) {
27                    // Subtract epsilon to avoid precision issues with ceiling function
28                    double timeWithRest = Math.ceil(dp[i - 1][j] - epsilon) + 
29                                          (double) dist[i - 1] / speed;
30                    dp[i][j] = Math.min(dp[i][j], timeWithRest);
31                }
32              
33                // Case 2: Skip the rest at (i-1)-th segment
34                // We continue immediately without waiting
35                if (j > 0) {
36                    double timeWithSkip = dp[i - 1][j - 1] + 
37                                          (double) dist[i - 1] / speed;
38                    dp[i][j] = Math.min(dp[i][j], timeWithSkip);
39                }
40            }
41        }
42      
43        // Find the minimum number of skips needed to arrive on time
44        for (int j = 0; j <= n; j++) {
45            // Add epsilon for floating point comparison tolerance
46            if (dp[n][j] <= hoursBefore + epsilon) {
47                return j;
48            }
49        }
50      
51        // If it's impossible to arrive on time even with all skips
52        return -1;
53    }
54}
55
1class Solution {
2public:
3    int minSkips(vector<int>& dist, int speed, int hoursBefore) {
4        int n = dist.size();
5      
6        // dp[i][j] represents the minimum time to complete first i roads with exactly j skips
7        // Initialize with a large value (infinity)
8        vector<vector<double>> dp(n + 1, vector<double>(n + 1, 1e20));
9      
10        // Base case: 0 roads with 0 skips takes 0 time
11        dp[0][0] = 0;
12      
13        // Small epsilon value to handle floating point precision issues
14        double epsilon = 1e-8;
15      
16        // Iterate through each road
17        for (int i = 1; i <= n; ++i) {
18            // Try different number of skips (j skips for i roads)
19            for (int j = 0; j <= i; ++j) {
20                // Option 1: Don't skip the rest at road i-1
21                // We need to wait (ceiling) if we're not at the last road
22                if (j < i) {
23                    // Calculate time if we rest after road i-1
24                    // Subtract epsilon before ceiling to avoid floating point errors
25                    double timeWithRest = ceil(dp[i - 1][j] + 
26                                              dist[i - 1] * 1.0 / speed - epsilon);
27                    dp[i][j] = min(dp[i][j], timeWithRest);
28                }
29              
30                // Option 2: Skip the rest at road i-1 (use one skip)
31                if (j > 0) {
32                    // Calculate time if we skip the rest after road i-1
33                    // No ceiling needed as we skip the rest
34                    double timeWithSkip = dp[i - 1][j - 1] + 
35                                         dist[i - 1] * 1.0 / speed;
36                    dp[i][j] = min(dp[i][j], timeWithSkip);
37                }
38            }
39        }
40      
41        // Find minimum number of skips needed to arrive before deadline
42        for (int j = 0; j <= n; ++j) {
43            // Check if we can complete all roads within hoursBefore with j skips
44            // Add epsilon for floating point comparison tolerance
45            if (dp[n][j] <= hoursBefore + epsilon) {
46                return j;
47            }
48        }
49      
50        // If impossible to arrive on time even with maximum skips
51        return -1;
52    }
53};
54
1/**
2 * Calculates the minimum number of skips needed to arrive on time
3 * @param dist - Array of distances between consecutive roads
4 * @param speed - Constant speed of travel
5 * @param hoursBefore - Maximum time allowed before the meeting
6 * @returns Minimum number of skips needed, or -1 if impossible
7 */
8function minSkips(dist: number[], speed: number, hoursBefore: number): number {
9    const roadCount: number = dist.length;
10  
11    // dp[i][j] represents the minimum time to complete first i roads with exactly j skips
12    // Initialize with Infinity to represent impossible states
13    const dp: number[][] = Array.from(
14        { length: roadCount + 1 }, 
15        () => Array.from({ length: roadCount + 1 }, () => Infinity)
16    );
17  
18    // Base case: 0 roads with 0 skips takes 0 time
19    dp[0][0] = 0;
20  
21    // Small epsilon value to handle floating point precision issues
22    const epsilon: number = 1e-8;
23  
24    // Fill the DP table
25    for (let currentRoad = 1; currentRoad <= roadCount; currentRoad++) {
26        for (let skipsUsed = 0; skipsUsed <= currentRoad; skipsUsed++) {
27            // Option 1: Rest after the previous road (no skip used)
28            if (skipsUsed < currentRoad) {
29                // Calculate time if we rest after road (currentRoad - 1)
30                // Subtract epsilon before ceiling to handle floating point errors
31                const timeWithRest: number = Math.ceil(
32                    dp[currentRoad - 1][skipsUsed] + dist[currentRoad - 1] / speed - epsilon
33                );
34                dp[currentRoad][skipsUsed] = Math.min(dp[currentRoad][skipsUsed], timeWithRest);
35            }
36          
37            // Option 2: Skip resting after the previous road (use one skip)
38            if (skipsUsed > 0) {
39                // Calculate time if we skip resting after road (currentRoad - 1)
40                const timeWithSkip: number = dp[currentRoad - 1][skipsUsed - 1] + dist[currentRoad - 1] / speed;
41                dp[currentRoad][skipsUsed] = Math.min(dp[currentRoad][skipsUsed], timeWithSkip);
42            }
43        }
44    }
45  
46    // Find the minimum number of skips that allows arrival within hoursBefore
47    for (let skipsCount = 0; skipsCount <= roadCount; skipsCount++) {
48        // Add epsilon to hoursBefore to handle floating point comparison
49        if (dp[roadCount][skipsCount] <= hoursBefore + epsilon) {
50            return skipsCount;
51        }
52    }
53  
54    // If no valid solution exists, return -1
55    return -1;
56}
57

Time and Space Complexity

The time complexity of this algorithm is O(n²), where n is the length of the dist array (number of roads).

This is because:

  • The outer loop iterates through each road position from 1 to n, giving us n iterations
  • For each road position i, the inner loop iterates from 0 to i, which in the worst case is also up to n iterations
  • Inside the nested loops, all operations (comparisons, arithmetic operations, min(), ceil()) are O(1)
  • Therefore, the overall time complexity is O(n) × O(n) × O(1) = O(n²)

The space complexity is O(n²), where n is the length of the dist array.

This is because:

  • We create a 2D array f with dimensions (n+1) × (n+1) to store the dynamic programming states
  • Each cell stores a single floating-point value
  • The total space required is (n+1) × (n+1) = O(n²)
  • Other variables like eps, i, j, and x use constant space O(1)
  • Therefore, the overall space complexity is dominated by the 2D array, giving us O(n²)

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

Common Pitfalls

1. Floating-Point Precision Errors

The most critical pitfall in this problem is handling floating-point arithmetic incorrectly. When dividing distances by speed, you get floating-point numbers that may have tiny precision errors.

Problem Example:

# Without epsilon adjustment
time = 1.9999999999999998  # Due to floating-point arithmetic
ceil(time) = 2  # Correct

time = 2.0000000000000004  # Due to floating-point arithmetic  
ceil(time) = 3  # WRONG! Should be 2

Solution: Always subtract a small epsilon (like 1e-8) before applying the ceiling function:

ceil(time - epsilon)  # Handles floating-point errors correctly

2. Incorrect DP Boundaries

A common mistake is setting incorrect loop boundaries or array dimensions.

Wrong Implementation:

# WRONG: Can't have more skips than roads minus 1
for num_skips in range(num_segments + 2):  # Too many possible skips
    ...

Correct Implementation:

# Maximum skips = n-1 (can't skip rest after the last road)
for num_skips in range(segment_idx + 1):  # Bounded by current segment
    ...

3. Forgetting the Last Road Special Case

The problem states that the last road doesn't require rest. Some implementations might incorrectly apply ceiling to the last road.

Wrong Approach:

# WRONG: Applying ceiling even for the last road
for i in range(1, n + 1):
    time = prev_time + dist[i-1] / speed
    dp[i][j] = ceil(time)  # Always ceiling

Correct Approach: The DP naturally handles this because:

  • We only apply ceiling when NOT skipping (adding rest)
  • The final answer checks dp[n][j] directly without additional ceiling
  • The last segment's time is included but no rest is added after it

4. Off-by-One Errors in Indexing

Mixing 0-based and 1-based indexing can lead to errors.

Common Mistake:

# Confusing dist[i] vs dist[i-1]
dp[i][j] = dp[i-1][j] + dist[i] / speed  # WRONG if i is 1-based

Correct Pattern:

for segment_idx, distance in enumerate(dist, 1):  # segment_idx is 1-based
    # Use 'distance' directly instead of dist[segment_idx-1]
    time = dp[segment_idx - 1][num_skips] + distance / speed

5. Not Handling the Impossible Case

Forgetting to return -1 when it's impossible to arrive on time.

Incomplete Solution:

# WRONG: Assumes a solution always exists
return min(j for j in range(n+1) if dp[n][j] <= hoursBefore)

Complete Solution:

for skips_used in range(num_segments + 1):
    if dp[num_segments][skips_used] <= hoursBefore + epsilon:
        return skips_used
return -1  # Important: Handle impossible case
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

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

Load More