2361. Minimum Costs Using the Train Line 🔒
Problem Description
You are traveling on a train line that has two routes through a city: a regular route and an express route. Both routes pass through the same n + 1
stops (labeled from 0
to n
). You start at stop 0
on the regular route.
You're given two arrays that describe the travel costs:
regular[i]
: the cost to travel from stopi-1
to stopi
using the regular routeexpress[i]
: the cost to travel from stopi-1
to stopi
using the express route
Both arrays are 1-indexed and have length n
.
There's also an expressCost
value, which is the fee you pay to switch from the regular route to the express route.
The switching rules are:
- Switching from regular to express costs
expressCost
(you pay this fee every time you make this switch) - Switching from express back to regular is free
- Staying on the express route has no additional cost
Your task is to find the minimum cost to reach each stop from stop 0
. Return a 1-indexed array costs
of length n
, where costs[i]
represents the minimum cost to reach stop i
from stop 0
.
The key insight is that at any stop, you can arrive via either route, and you want to track the cheapest way to get there. The dynamic programming approach maintains two states:
f[i]
: minimum cost to reach stopi
via the regular routeg[i]
: minimum cost to reach stopi
via the express route
For each stop, you calculate both possibilities and take the minimum of the two as your answer for that stop.
Intuition
The key observation is that at each stop, we need to track which route we're currently on because the cost of future transitions depends on our current route. If we're on the regular route, we can either continue on it for free or pay expressCost
to switch to express. If we're on the express route, we can continue on it or switch back to regular for free.
This naturally leads to a dynamic programming approach where we maintain two separate states for each stop - one for arriving via regular route and one for arriving via express route. Why two states? Because the minimum cost to reach a stop might differ based on which route we use to arrive there, and this affects our future choices.
Think of it like this: even if arriving at stop i
via express is more expensive than via regular, it might still be worth it if the express route offers significant savings for stops i+1
, i+2
, etc. We can't just greedily pick the cheapest option at each stop - we need to consider both possibilities.
For each stop i
, we calculate:
- The minimum cost to arrive via regular: either we were already on regular (cost is
f[i-1] + regular[i]
) or we were on express and stayed on regular when moving to this stop (cost isg[i-1] + regular[i]
) - The minimum cost to arrive via express: either we switch from regular to express (cost is
f[i-1] + expressCost + express[i]
) or we were already on express (cost isg[i-1] + express[i]
)
The answer for each stop is simply the minimum of these two values, since we can reach any stop from either route. This builds up our solution incrementally from stop 0
to stop n
, ensuring we always have the optimal cost for each stop considering all possible route combinations.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement a dynamic programming solution using two arrays to track the minimum costs for each route:
-
Initialize the DP arrays:
f[i]
represents the minimum cost to reach stopi
via the regular routeg[i]
represents the minimum cost to reach stopi
via the express route- Set
f[0] = 0
(we start on regular route at stop 0) - Set
g[0] = inf
(impossible to start on express route)
-
Build the solution iteratively: For each stop
i
from 1 to n, we calculate bothf[i]
andg[i]
:For arriving via regular route:
f[i] = min(f[i-1] + regular[i], g[i-1] + regular[i])
This means we take the minimum of:
- Coming from stop
i-1
on regular and continuing on regular:f[i-1] + regular[i]
- Coming from stop
i-1
on express and switching to regular (free):g[i-1] + regular[i]
For arriving via express route:
g[i] = min(f[i-1] + expressCost + express[i], g[i-1] + express[i])
This means we take the minimum of:
- Coming from stop
i-1
on regular and switching to express:f[i-1] + expressCost + express[i]
- Coming from stop
i-1
on express and continuing on express:g[i-1] + express[i]
- Coming from stop
-
Construct the answer: For each stop
i
, the minimum cost is:cost[i-1] = min(f[i], g[i])
We use
i-1
because the output array is 1-indexed while our iteration uses 0-indexed stops.
The algorithm processes each stop exactly once, computing two values (regular and express costs) per stop. The space complexity is O(n) for storing the DP arrays, and the time complexity is O(n) since we iterate through all stops once.
The implementation uses zip(regular, express)
with enumerate(_, 1)
to elegantly iterate through both arrays simultaneously while maintaining the correct 1-based indexing for stops.
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 small example to illustrate the solution approach.
Input:
regular = [1, 6, 9, 5]
(costs to travel via regular route)express = [5, 2, 3, 10]
(costs to travel via express route)expressCost = 3
(fee to switch from regular to express)
We need to find the minimum cost to reach stops 1, 2, 3, and 4 from stop 0.
Initial Setup:
f[0] = 0
(start at stop 0 on regular route)g[0] = inf
(cannot start on express route)
Stop 1:
- To arrive via regular:
f[1] = min(f[0] + regular[1], g[0] + regular[1]) = min(0 + 1, inf + 1) = 1
- To arrive via express:
g[1] = min(f[0] + expressCost + express[1], g[0] + express[1]) = min(0 + 3 + 5, inf + 5) = 8
- Minimum cost to reach stop 1:
min(1, 8) = 1
Stop 2:
- To arrive via regular:
f[2] = min(f[1] + regular[2], g[1] + regular[2]) = min(1 + 6, 8 + 6) = 7
- To arrive via express:
g[2] = min(f[1] + expressCost + express[2], g[1] + express[2]) = min(1 + 3 + 2, 8 + 2) = 6
- Minimum cost to reach stop 2:
min(7, 6) = 6
Stop 3:
- To arrive via regular:
f[3] = min(f[2] + regular[3], g[2] + regular[3]) = min(7 + 9, 6 + 9) = 15
- To arrive via express:
g[3] = min(f[2] + expressCost + express[3], g[2] + express[3]) = min(7 + 3 + 3, 6 + 3) = 9
- Minimum cost to reach stop 3:
min(15, 9) = 9
Stop 4:
- To arrive via regular:
f[4] = min(f[3] + regular[4], g[3] + regular[4]) = min(15 + 5, 9 + 5) = 14
- To arrive via express:
g[4] = min(f[3] + expressCost + express[4], g[3] + express[4]) = min(15 + 3 + 10, 9 + 10) = 19
- Minimum cost to reach stop 4:
min(14, 19) = 14
Final Answer: [1, 6, 9, 14]
The optimal path in this example:
- Stop 0 → Stop 1: Use regular route (cost: 1)
- Stop 1 → Stop 2: Switch to express (cost: 3 + 2 = 5, total: 6)
- Stop 2 → Stop 3: Stay on express (cost: 3, total: 9)
- Stop 3 → Stop 4: Switch back to regular for free (cost: 5, total: 14)
Solution Implementation
1from typing import List
2from math import inf
3
4
5class Solution:
6 def minimumCosts(
7 self, regular: List[int], express: List[int], expressCost: int
8 ) -> List[int]:
9 """
10 Calculate minimum costs to reach each stop using either regular or express lane.
11
12 Args:
13 regular: Cost to travel each segment on regular lane
14 express: Cost to travel each segment on express lane
15 expressCost: One-time cost to switch from regular to express lane
16
17 Returns:
18 List of minimum costs to reach each stop (1-indexed positions)
19 """
20 n = len(regular)
21
22 # Dynamic programming arrays
23 # regular_min_cost[i]: minimum cost to reach position i using regular lane
24 regular_min_cost = [0] * (n + 1)
25
26 # express_min_cost[i]: minimum cost to reach position i using express lane
27 express_min_cost = [inf] * (n + 1)
28
29 # Result array to store minimum cost to reach each stop
30 result = [0] * n
31
32 # Process each stop
33 for i in range(1, n + 1):
34 regular_cost = regular[i - 1]
35 express_cost = express[i - 1]
36
37 # Calculate minimum cost to reach position i using regular lane
38 # Option 1: Stay on regular lane from position i-1
39 # Option 2: Switch from express lane to regular lane (no switching cost)
40 regular_min_cost[i] = min(
41 regular_min_cost[i - 1] + regular_cost,
42 express_min_cost[i - 1] + regular_cost
43 )
44
45 # Calculate minimum cost to reach position i using express lane
46 # Option 1: Switch from regular lane to express lane (pay switching cost)
47 # Option 2: Stay on express lane from position i-1
48 express_min_cost[i] = min(
49 regular_min_cost[i - 1] + expressCost + express_cost,
50 express_min_cost[i - 1] + express_cost
51 )
52
53 # Store the minimum cost between both lanes for position i
54 result[i - 1] = min(regular_min_cost[i], express_min_cost[i])
55
56 return result
57
1class Solution {
2 public long[] minimumCosts(int[] regular, int[] express, int expressCost) {
3 int n = regular.length;
4
5 // dp arrays for tracking minimum costs
6 // regularPathCost[i] = minimum cost to reach station i using regular path at station i
7 long[] regularPathCost = new long[n + 1];
8 // expressPathCost[i] = minimum cost to reach station i using express path at station i
9 long[] expressPathCost = new long[n + 1];
10
11 // Initialize: starting with express path at station 0 is impossible (set to large value)
12 expressPathCost[0] = 1 << 30;
13
14 // Result array to store minimum cost to reach each station
15 long[] result = new long[n];
16
17 // Process each station from 1 to n
18 for (int i = 1; i <= n; i++) {
19 // Get costs for current segment (0-indexed in input arrays)
20 int regularSegmentCost = regular[i - 1];
21 int expressSegmentCost = express[i - 1];
22
23 // Calculate minimum cost to reach station i using regular path
24 // Can come from either regular path or express path at previous station
25 regularPathCost[i] = Math.min(
26 regularPathCost[i - 1] + regularSegmentCost, // Continue on regular path
27 expressPathCost[i - 1] + regularSegmentCost // Switch from express to regular
28 );
29
30 // Calculate minimum cost to reach station i using express path
31 // Can switch from regular (pay expressCost) or continue on express
32 expressPathCost[i] = Math.min(
33 regularPathCost[i - 1] + expressCost + expressSegmentCost, // Switch from regular to express
34 expressPathCost[i - 1] + expressSegmentCost // Continue on express path
35 );
36
37 // Store the minimum cost to reach station i (regardless of path type)
38 result[i - 1] = Math.min(regularPathCost[i], expressPathCost[i]);
39 }
40
41 return result;
42 }
43}
44
1class Solution {
2public:
3 vector<long long> minimumCosts(vector<int>& regular, vector<int>& express, int expressCost) {
4 int n = regular.size();
5
6 // Dynamic programming arrays
7 // regularLaneCost[i]: minimum cost to reach stop i using regular lane
8 // expressLaneCost[i]: minimum cost to reach stop i using express lane
9 vector<long long> regularLaneCost(n + 1);
10 vector<long long> expressLaneCost(n + 1);
11
12 // Base case initialization
13 regularLaneCost[0] = 0; // Start at stop 0 with regular lane (no cost)
14 expressLaneCost[0] = 1LL << 30; // Cannot start directly in express lane (set to large value)
15
16 // Result array to store minimum cost to reach each stop
17 vector<long long> result(n);
18
19 // Process each stop from 1 to n
20 for (int i = 1; i <= n; ++i) {
21 int regularCostToNext = regular[i - 1];
22 int expressCostToNext = express[i - 1];
23
24 // Calculate minimum cost to reach stop i using regular lane
25 // Option 1: Continue from previous stop in regular lane
26 // Option 2: Switch from express lane to regular lane (no switching cost)
27 regularLaneCost[i] = min(regularLaneCost[i - 1] + regularCostToNext,
28 expressLaneCost[i - 1] + regularCostToNext);
29
30 // Calculate minimum cost to reach stop i using express lane
31 // Option 1: Switch from regular lane to express lane (pay expressCost)
32 // Option 2: Continue from previous stop in express lane
33 expressLaneCost[i] = min(regularLaneCost[i - 1] + expressCost + expressCostToNext,
34 expressLaneCost[i - 1] + expressCostToNext);
35
36 // Store the minimum cost to reach stop i (minimum of both lanes)
37 result[i - 1] = min(regularLaneCost[i], expressLaneCost[i]);
38 }
39
40 return result;
41 }
42};
43
1/**
2 * Calculates minimum costs to reach each stop using regular or express routes
3 * @param regular - Array of costs for regular route between consecutive stops
4 * @param express - Array of costs for express route between consecutive stops
5 * @param expressCost - One-time fee to enter the express route
6 * @returns Array of minimum costs to reach each stop
7 */
8function minimumCosts(regular: number[], express: number[], expressCost: number): number[] {
9 const numberOfStops: number = regular.length;
10
11 // Dynamic programming arrays to track minimum costs
12 // regularRouteCosts[i] = minimum cost to reach stop i using regular route
13 const regularRouteCosts: number[] = new Array(numberOfStops + 1).fill(0);
14
15 // expressRouteCosts[i] = minimum cost to reach stop i using express route
16 const expressRouteCosts: number[] = new Array(numberOfStops + 1).fill(0);
17
18 // Initialize express route at start with a large value (impossible to start on express)
19 expressRouteCosts[0] = 1 << 30;
20
21 // Result array to store minimum cost to reach each stop
22 const minimumCostToStop: number[] = new Array(numberOfStops).fill(0);
23
24 // Process each stop
25 for (let stopIndex = 1; stopIndex <= numberOfStops; ++stopIndex) {
26 // Get costs for current segment (0-indexed arrays)
27 const regularSegmentCost: number = regular[stopIndex - 1];
28 const expressSegmentCost: number = express[stopIndex - 1];
29
30 // Calculate minimum cost to reach current stop via regular route
31 // Can come from either previous regular route or switch from express route
32 regularRouteCosts[stopIndex] = Math.min(
33 regularRouteCosts[stopIndex - 1] + regularSegmentCost,
34 expressRouteCosts[stopIndex - 1] + regularSegmentCost
35 );
36
37 // Calculate minimum cost to reach current stop via express route
38 // Can switch from regular (paying express fee) or continue on express
39 expressRouteCosts[stopIndex] = Math.min(
40 regularRouteCosts[stopIndex - 1] + expressCost + expressSegmentCost,
41 expressRouteCosts[stopIndex - 1] + expressSegmentCost
42 );
43
44 // Store the minimum cost between both routes for current stop
45 minimumCostToStop[stopIndex - 1] = Math.min(
46 regularRouteCosts[stopIndex],
47 expressRouteCosts[stopIndex]
48 );
49 }
50
51 return minimumCostToStop;
52}
53
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through the input arrays exactly once using a single for loop that runs from index 1 to n. Within each iteration, the operations performed are:
- Two minimum comparisons for calculating
f[i]
- Two minimum comparisons for calculating
g[i]
- One minimum comparison for calculating
cost[i-1]
All these operations take constant time O(1)
. Since the loop runs n times and each iteration performs constant work, the overall time complexity is O(n)
.
Space Complexity: O(n)
The algorithm uses three arrays:
- Array
f
of sizen + 1
to store the minimum cost to reach each station using the regular lane - Array
g
of sizen + 1
to store the minimum cost to reach each station using the express lane - Array
cost
of sizen
to store the final minimum costs
Each array requires O(n)
space, and since we're using a constant number of such arrays, the total space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Initialization of Express Lane Cost
One of the most common mistakes is initializing express_min_cost[0]
with 0 instead of infinity. This would incorrectly assume that you can start on the express lane at no cost, when in reality you must start on the regular lane.
Incorrect:
express_min_cost = [0] * (n + 1) # Wrong! Can't start on express lane
Correct:
express_min_cost = [inf] * (n + 1) # Correct - impossible to start on express
2. Forgetting the Express Switch Cost
Another frequent error is forgetting to add expressCost
when switching from regular to express lane. This leads to underestimating the total cost.
Incorrect:
express_min_cost[i] = min(
regular_min_cost[i - 1] + express_cost, # Missing expressCost!
express_min_cost[i - 1] + express_cost
)
Correct:
express_min_cost[i] = min(
regular_min_cost[i - 1] + expressCost + express_cost, # Include switch cost
express_min_cost[i - 1] + express_cost
)
3. Index Confusion Between 0-based and 1-based Arrays
The problem uses 1-indexed arrays for regular
and express
, but the DP arrays are 0-indexed. Mixing these up can cause off-by-one errors or array out-of-bounds exceptions.
Incorrect:
for i in range(n): # Wrong iteration range
regular_cost = regular[i] # Wrong index
result[i] = min(regular_min_cost[i], express_min_cost[i]) # Wrong result index
Correct:
for i in range(1, n + 1): # Iterate from 1 to n
regular_cost = regular[i - 1] # Access 1-indexed array correctly
result[i - 1] = min(regular_min_cost[i], express_min_cost[i]) # Store in 0-indexed result
4. Not Considering Both Routes for Final Answer
Some implementations might only return the cost of reaching each stop via one route, forgetting to take the minimum of both routes.
Incorrect:
result[i - 1] = regular_min_cost[i] # Only considering regular route
Correct:
result[i - 1] = min(regular_min_cost[i], express_min_cost[i]) # Consider both routes
5. Space Optimization Pitfall
While it's tempting to optimize space by using only the previous state, this problem requires careful handling since each state depends on both previous states. Attempting to use single variables instead of arrays without proper care can lead to incorrect results.
Problematic optimization:
# Trying to use only two variables instead of arrays prev_regular = 0 prev_express = inf # This approach requires careful variable management to avoid overwriting needed values
Better approach: Keep the full arrays as shown in the solution, or if space optimization is needed, use a rolling array technique with proper temporary variables to avoid overwriting values before they're used.
What are the most two important steps in writing a depth first search function? (Select 2)
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!