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:
-
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
-
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
-
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.
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:
- Not skip the rest: We add the road's travel time and round up to the next integer hour (using ceiling function)
- 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):
-
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
- Only valid when
-
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
- Only valid when
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 EvaluatorExample 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.0f[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.0f[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.0f[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
ton
, giving usn
iterations - For each road position
i
, the inner loop iterates from0
toi
, which in the worst case is also up ton
iterations - Inside the nested loops, all operations (comparisons, arithmetic operations,
min()
,ceil()
) areO(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
, andx
use constant spaceO(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
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!