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.
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:
- We have overlapping subproblems - we might reach the same city at the same time through different paths
- 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 cityy
ini - t
minutes, then taking this edge (addingpassingFees[x]
) - Reach city
y
by first getting to cityx
ini - t
minutes, then taking this edge (addingpassingFees[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 city0
at time0
and must pay its passing fee - All other
f[0][j]
remain infinity since we cannot reach any other city in0
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 timei
by being at cityy
at timei - t
, then taking the edge tox
- This means: reach city
- Update
f[i][y] = min(f[i][y], f[i - t][x] + passingFees[y])
- This means: reach city
y
at timei
by being at cityx
at timei - t
, then taking the edge toy
- This means: reach city
- Update
Finding the Answer:
- After filling the DP table, check all possible times from
0
tomaxTime
- 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 EvaluatorExample 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
- From city 1 at time 10 → city 0:
- Edge (1,2,10):
- From city 1 at time 10 → city 2:
f[20][2] = min(∞, 6 + 2) = 8
- From city 1 at time 10 → city 2:
- 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
- From city 0 at time 20 → city 1:
- Edge (1,2,10):
- From city 2 at time 20 → city 1:
f[30][1] = min(12, 8 + 1) = 9
- From city 2 at time 20 → city 1:
- Edge (2,3,10):
- From city 2 at time 20 → city 3:
f[30][3] = min(∞, 8 + 20) = 28
- From city 2 at time 20 → city 3:
- 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 tom
) - 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 tomaxTime
) - 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)]
How many times is a tree node visited in a depth first search?
Recommended Readings
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
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
Want a Structured Path to Master System Design Too? Don’t Miss This!