Facebook Pixel

1928. Minimum Cost to Reach Destination in Time

Problem Description

You have a country with n cities numbered from 0 to n - 1. All cities are connected by bi-directional roads. The road network is given as a 2D array edges where edges[i] = [xi, yi, timei] represents a road between cities xi and yi that takes timei minutes to travel. There can be multiple roads with different travel times between the same two cities, but no road connects a city to itself.

Each city has a passing fee that you must pay whenever you pass through it (including your starting and ending cities). These fees are given in the array passingFees where passingFees[j] is the cost in dollars to pass through city j.

You start at city 0 and need to reach city n - 1 within maxTime minutes or less. The total cost of your journey is the sum of all passing fees for every city you pass through during your trip.

Your task is to find the minimum cost to complete the journey from city 0 to city n - 1 within the time limit. If it's impossible to reach the destination within maxTime minutes, return -1.

For example, if you travel from city 0 → city 2 → city 3, you would pay passingFees[0] + passingFees[2] + passingFees[3] as your total cost.

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 minimum cost path while considering both the time constraint and the passing fees. This is a constrained shortest path problem where we have two dimensions to optimize: time and cost.

We can think of this problem as exploring different states where each state represents "being at a certain city after spending a certain amount of time." Since we want to minimize cost while respecting the time limit, we need to track the minimum cost to reach each city at different time points.

The natural approach is dynamic programming because:

  1. We have overlapping subproblems - we might reach the same city at the same time through different paths
  2. We need to make optimal decisions - among all ways to reach a city within a time limit, we want the cheapest one

The state can be defined as f[i][j] = minimum cost to reach city j using exactly i minutes. Why track exact time instead of "within i minutes"? Because a path that reaches a city earlier might enable us to take a different route later that leads to a better overall solution.

For each time step from 1 to maxTime, we try all possible edges. For an edge (x, y, t), if we have t ≤ i minutes available, we can:

  • Reach city x by first getting to city y in i - t minutes, then taking this edge (adding passingFees[x])
  • Reach city y by first getting to city x in i - t minutes, then taking this edge (adding passingFees[y])

This builds up all possible ways to reach each city at each time point, keeping only the minimum cost for each state. Finally, we check all time points from 0 to maxTime and find the minimum cost to reach city n - 1.

Learn more about Graph and Dynamic Programming patterns.

Solution Approach

We implement the dynamic programming solution using a 2D table f where f[i][j] represents the minimum cost to reach city j from city 0 in exactly i minutes.

Initialization:

  • Create a 2D array f of size (maxTime + 1) × n initialized with infinity (inf)
  • Set f[0][0] = passingFees[0] since we start at city 0 at time 0 and must pay its passing fee
  • All other f[0][j] remain infinity since we cannot reach any other city in 0 minutes

State Transition: For each time i from 1 to maxTime:

  • Iterate through all edges (x, y, t)
  • If the edge time t ≤ i (we have enough time to use this edge):
    • Update f[i][x] = min(f[i][x], f[i - t][y] + passingFees[x])
      • This means: reach city x at time i by being at city y at time i - t, then taking the edge to x
    • Update f[i][y] = min(f[i][y], f[i - t][x] + passingFees[y])
      • This means: reach city y at time i by being at city x at time i - t, then taking the edge to y

Finding the Answer:

  • After filling the DP table, check all possible times from 0 to maxTime
  • Find the minimum value among f[0][n-1], f[1][n-1], ..., f[maxTime][n-1]
  • If this minimum is still infinity, return -1 (impossible to reach destination)
  • Otherwise, return the minimum cost found

Time Complexity: O(maxTime × |edges|) since we iterate through all edges for each time step

Space Complexity: O(maxTime × n) for the DP table

The algorithm correctly handles multiple edges between cities and ensures we only consider paths that respect the time constraint while minimizing the total passing fees.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example Input:

  • n = 4 (cities: 0, 1, 2, 3)
  • edges = [[0,1,10], [1,2,10], [2,3,10], [0,3,100]]
  • passingFees = [5, 1, 2, 20]
  • maxTime = 30

We need to find the minimum cost to travel from city 0 to city 3 within 30 minutes.

Step 1: Initialize DP Table Create a table f[time][city] where f[i][j] = minimum cost to reach city j in exactly i minutes.

Initially:

  • f[0][0] = 5 (start at city 0, pay its fee)
  • All other entries = ∞

Step 2: Fill DP Table

For time = 10:

  • Edge (0,1,10): Can reach city 1 from city 0
    • f[10][1] = f[0][0] + passingFees[1] = 5 + 1 = 6
  • Edge (1,2,10): Cannot use (would need to be at city 1 or 2 at time 0)
  • Edge (2,3,10): Cannot use
  • Edge (0,3,100): Cannot use (needs 100 minutes)

State after time 10: f[10][0]=∞, f[10][1]=6, f[10][2]=∞, f[10][3]=∞

For time = 20:

  • Edge (0,1,10):
    • From city 1 at time 10 → city 0: f[20][0] = min(∞, 6 + 5) = 11
  • Edge (1,2,10):
    • From city 1 at time 10 → city 2: f[20][2] = min(∞, 6 + 2) = 8
  • Edge (2,3,10): Cannot use (no way to reach city 2 or 3 at time 10)
  • Edge (0,3,100): Cannot use

State after time 20: f[20][0]=11, f[20][1]=∞, f[20][2]=8, f[20][3]=∞

For time = 30:

  • Edge (0,1,10):
    • From city 0 at time 20 → city 1: f[30][1] = min(∞, 11 + 1) = 12
  • Edge (1,2,10):
    • From city 2 at time 20 → city 1: f[30][1] = min(12, 8 + 1) = 9
  • Edge (2,3,10):
    • From city 2 at time 20 → city 3: f[30][3] = min(∞, 8 + 20) = 28
  • Edge (0,3,100): Cannot use

State after time 30: f[30][0]=∞, f[30][1]=9, f[30][2]=∞, f[30][3]=28

Step 3: Find Minimum Cost to Reach City 3 Check all times from 0 to 30 for reaching city 3:

  • f[0][3] = ∞
  • f[10][3] = ∞
  • f[20][3] = ∞
  • f[30][3] = 28

The minimum cost is 28, achieved by the path: city 0 → city 1 → city 2 → city 3

  • Total time: 10 + 10 + 10 = 30 minutes
  • Total cost: 5 (city 0) + 1 (city 1) + 2 (city 2) + 20 (city 3) = 28

Note that the direct path 0 → 3 would take 100 minutes, exceeding our time limit, so it's not considered.

Solution Implementation

1class Solution:
2    def minCost(
3        self, maxTime: int, edges: List[List[int]], passingFees: List[int]
4    ) -> int:
5        """
6        Find minimum cost to reach the last city within maxTime.
7      
8        Args:
9            maxTime: Maximum time allowed for travel
10            edges: List of [city1, city2, time] representing bidirectional roads
11            passingFees: List of fees for passing through each city
12          
13        Returns:
14            Minimum cost to reach city n-1 from city 0, or -1 if impossible
15        """
16        max_time = maxTime
17        num_cities = len(passingFees)
18      
19        # dp[time][city] = minimum cost to reach city at exact time
20        # Initialize with infinity for all states
21        dp = [[float('inf')] * num_cities for _ in range(max_time + 1)]
22      
23        # Base case: start at city 0 at time 0 with its passing fee
24        dp[0][0] = passingFees[0]
25      
26        # Fill DP table for each time step
27        for time in range(1, max_time + 1):
28            # Process each edge to update minimum costs
29            for city1, city2, travel_time in edges:
30                # Check if we have enough time to use this edge
31                if travel_time <= time:
32                    # Update cost to reach city1 from city2
33                    dp[time][city1] = min(
34                        dp[time][city1], 
35                        dp[time - travel_time][city2] + passingFees[city1]
36                    )
37                    # Update cost to reach city2 from city1 (bidirectional)
38                    dp[time][city2] = min(
39                        dp[time][city2], 
40                        dp[time - travel_time][city1] + passingFees[city2]
41                    )
42      
43        # Find minimum cost to reach the last city across all valid times
44        min_cost = min(dp[time][num_cities - 1] for time in range(max_time + 1))
45      
46        # Return result: minimum cost if reachable, otherwise -1
47        return min_cost if min_cost < float('inf') else -1
48
1class Solution {
2    public int minCost(int maxTime, int[][] edges, int[] passingFees) {
3        int timeLimit = maxTime;
4        int numCities = passingFees.length;
5      
6        // dp[time][city] = minimum cost to reach city at exactly time units
7        int[][] dp = new int[timeLimit + 1][numCities];
8      
9        // Initialize with a large value representing infinity
10        final int INFINITY = 1 << 30;
11        for (int[] row : dp) {
12            Arrays.fill(row, INFINITY);
13        }
14      
15        // Base case: start at city 0 with time 0, cost is the passing fee of city 0
16        dp[0][0] = passingFees[0];
17      
18        // Fill the DP table for each time step
19        for (int currentTime = 1; currentTime <= timeLimit; currentTime++) {
20            // Process each edge
21            for (int[] edge : edges) {
22                int city1 = edge[0];
23                int city2 = edge[1];
24                int travelTime = edge[2];
25              
26                // Check if we have enough time to use this edge
27                if (travelTime <= currentTime) {
28                    // Update minimum cost to reach city1 from city2
29                    dp[currentTime][city1] = Math.min(
30                        dp[currentTime][city1], 
31                        dp[currentTime - travelTime][city2] + passingFees[city1]
32                    );
33                  
34                    // Update minimum cost to reach city2 from city1 (bidirectional edge)
35                    dp[currentTime][city2] = Math.min(
36                        dp[currentTime][city2], 
37                        dp[currentTime - travelTime][city1] + passingFees[city2]
38                    );
39                }
40            }
41        }
42      
43        // Find the minimum cost to reach the last city (n-1) within the time limit
44        int minCost = INFINITY;
45        for (int time = 0; time <= timeLimit; time++) {
46            minCost = Math.min(minCost, dp[time][numCities - 1]);
47        }
48      
49        // Return -1 if destination is unreachable, otherwise return the minimum cost
50        return minCost == INFINITY ? -1 : minCost;
51    }
52}
53
1class Solution {
2public:
3    int minCost(int maxTime, vector<vector<int>>& edges, vector<int>& passingFees) {
4        int timeLimit = maxTime;
5        int numCities = passingFees.size();
6        const int INF = 1 << 30;
7      
8        // dp[time][city] = minimum cost to reach city at exactly 'time' units
9        vector<vector<int>> dp(timeLimit + 1, vector<int>(numCities, INF));
10      
11        // Base case: start at city 0 with time 0, paying its passing fee
12        dp[0][0] = passingFees[0];
13      
14        // Fill dp table for each time unit
15        for (int currentTime = 1; currentTime <= timeLimit; ++currentTime) {
16            // Try all edges as possible transitions
17            for (const auto& edge : edges) {
18                int city1 = edge[0];
19                int city2 = edge[1];
20                int travelTime = edge[2];
21              
22                // Check if we have enough time to use this edge
23                if (travelTime <= currentTime) {
24                    // Update minimum cost to reach city1 from city2
25                    dp[currentTime][city1] = min(
26                        dp[currentTime][city1], 
27                        dp[currentTime - travelTime][city2] + passingFees[city1]
28                    );
29                  
30                    // Update minimum cost to reach city2 from city1 (bidirectional edge)
31                    dp[currentTime][city2] = min(
32                        dp[currentTime][city2], 
33                        dp[currentTime - travelTime][city1] + passingFees[city2]
34                    );
35                }
36            }
37        }
38      
39        // Find minimum cost to reach destination (city n-1) within time limit
40        int minCostToDestination = INF;
41        for (int time = 1; time <= timeLimit; ++time) {
42            minCostToDestination = min(minCostToDestination, dp[time][numCities - 1]);
43        }
44      
45        // Return -1 if destination is unreachable, otherwise return minimum cost
46        return minCostToDestination == INF ? -1 : minCostToDestination;
47    }
48};
49
1/**
2 * Finds the minimum cost to travel from city 0 to city n-1 within the given time limit
3 * @param maxTime - Maximum time allowed for the journey
4 * @param edges - Array of edges where each edge is [city1, city2, time]
5 * @param passingFees - Array of fees for passing through each city
6 * @returns Minimum cost to reach destination, or -1 if impossible
7 */
8function minCost(maxTime: number, edges: number[][], passingFees: number[]): number {
9    const timeLimit: number = maxTime;
10    const cityCount: number = passingFees.length;
11  
12    // dp[time][city] = minimum cost to reach city at exactly time units
13    const dp: number[][] = Array.from(
14        { length: timeLimit + 1 }, 
15        () => Array(cityCount).fill(Infinity)
16    );
17  
18    // Initialize starting position: at time 0, we're at city 0 with its passing fee
19    dp[0][0] = passingFees[0];
20  
21    // Fill the DP table for each time step
22    for (let currentTime = 1; currentTime <= timeLimit; currentTime++) {
23        // Process each edge to update possible paths
24        for (const [cityA, cityB, travelTime] of edges) {
25            // Only process if we have enough time for this edge
26            if (travelTime <= currentTime) {
27                // Update cost to reach cityA from cityB
28                dp[currentTime][cityA] = Math.min(
29                    dp[currentTime][cityA], 
30                    dp[currentTime - travelTime][cityB] + passingFees[cityA]
31                );
32              
33                // Update cost to reach cityB from cityA (bidirectional edge)
34                dp[currentTime][cityB] = Math.min(
35                    dp[currentTime][cityB], 
36                    dp[currentTime - travelTime][cityA] + passingFees[cityB]
37                );
38            }
39        }
40    }
41  
42    // Find the minimum cost to reach the destination city (n-1) at any valid time
43    let minimumCost: number = Infinity;
44    for (let time = 1; time <= timeLimit; time++) {
45        minimumCost = Math.min(minimumCost, dp[time][cityCount - 1]);
46    }
47  
48    // Return -1 if destination is unreachable, otherwise return the minimum cost
49    return minimumCost === Infinity ? -1 : minimumCost;
50}
51

Time and Space Complexity

Time Complexity: O(maxTime × m), where m is the number of edges.

The algorithm uses dynamic programming with two nested loops:

  • The outer loop runs maxTime iterations (from 1 to m)
  • The inner loop iterates through all edges, which takes O(m) time per iteration
  • Inside the inner loop, each edge results in constant time operations (comparisons and updates)
  • The final step to find the minimum takes O(maxTime) time, which is dominated by the main loops

Therefore, the total time complexity is O(maxTime × m).

Space Complexity: O(maxTime × n), where n is the number of cities (nodes).

The algorithm creates a 2D DP table f with dimensions (maxTime + 1) × n:

  • The table has maxTime + 1 rows (one for each time from 0 to maxTime)
  • Each row contains n elements (one for each city)
  • No additional significant space is used beyond this table

Therefore, the space complexity is O(maxTime × n).

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

Common Pitfalls

1. Forgetting to Initialize Starting City Cost

A critical mistake is not properly initializing the starting city's cost. Some might set dp[0][0] = 0 instead of dp[0][0] = passingFees[0], forgetting that we must pay the passing fee for the starting city as well.

Wrong:

dp[0][0] = 0  # Incorrect - misses the starting city fee

Correct:

dp[0][0] = passingFees[0]  # Must pay fee for starting city

2. Not Handling Bidirectional Edges Properly

Since roads are bidirectional, each edge can be traversed in both directions. A common mistake is only updating one direction in the DP transition.

Wrong:

for city1, city2, travel_time in edges:
    if travel_time <= time:
        # Only updating one direction
        dp[time][city2] = min(
            dp[time][city2], 
            dp[time - travel_time][city1] + passingFees[city2]
        )

Correct:

for city1, city2, travel_time in edges:
    if travel_time <= time:
        # Update both directions
        dp[time][city1] = min(
            dp[time][city1], 
            dp[time - travel_time][city2] + passingFees[city1]
        )
        dp[time][city2] = min(
            dp[time][city2], 
            dp[time - travel_time][city1] + passingFees[city2]
        )

3. Incorrect Time Constraint Check

Some might check travel_time < time instead of travel_time <= time, which would miss valid paths where the travel time exactly equals the current time being considered.

Wrong:

if travel_time < time:  # Misses edge case where travel_time == time

Correct:

if travel_time <= time:  # Correctly includes all valid travel times

4. Using Wrong DP State Definition

A subtle but important pitfall is misunderstanding what the DP state represents. The state dp[i][j] should represent the minimum cost to reach city j in at most i time (or exactly i time in this implementation). Mixing up this definition can lead to incorrect transitions.

5. Memory Optimization Trap

While it might be tempting to optimize space by using only two arrays (current and previous time), this can lead to errors if not implemented carefully, as we need to ensure we don't overwrite values we still need to read.

Risky optimization:

# Using only current and previous arrays can cause issues
prev = [float('inf')] * num_cities
curr = [float('inf')] * num_cities
# Need to be very careful about when values are updated

Safe approach (as in the solution):

# Full 2D array is clearer and less error-prone
dp = [[float('inf')] * num_cities for _ in range(max_time + 1)]
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

How many times is a tree node visited in a depth first search?


Recommended Readings

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

Load More