Problem description:

You have planned some train traveling one year in advance. The days of the year in which you will travel are given as an integer array days. Each day is an integer from 1 to 365.

Train tickets are sold in three different ways:

  • a 1-day pass is sold for costs[0] dollars,
  • a 7-day pass is sold for costs[1] dollars, and
  • a 30-day pass is sold for costs[2] dollars.

The passes allow that many days of consecutive travel.

  • For example, if we get a 7-day pass on day 2, then we can travel for 7 days: 2, 3, 4, 5, 6, 7, and 8.

Return the minimum number of dollars you need to travel every day in the given list of days.

Example 1:

1
2
3
4
5
6
7
Input: days = [1,4,6,7,8,20], costs = [2,7,15]
Output: 11
Explanation: For example, here is one way to buy passes that lets you travel your travel plan:
On day 1, you bought a 1-day pass for costs[0] = $2, which covered day 1.
On day 3, you bought a 7-day pass for costs[1] = $7, which covered days 3, 4, ..., 9.
On day 20, you bought a 1-day pass for costs[0] = $2, which covered day 20.
In total, you spent $11 and covered all the days of your travel.

Example 2:

1
2
3
4
5
6
Input: days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
Output: 17
Explanation: For example, here is one way to buy passes that lets you travel your travel plan:
On day 1, you bought a 30-day pass for costs[2] = $15 which covered days 1, 2, ..., 30.
On day 31, you bought a 1-day pass for costs[0] = $2 which covered day 31.
In total, you spent $17 and covered all the days of your travel.

Constraints:

  • 1 <= days.length <= 365
  • 1 <= days[i] <= 365
  • days is in strictly increasing order.
  • costs.length == 3
  • 1 <= costs[i] <= 1000

Solution:

Top down DP

Calculating the cost for upcoming days

costDay = cost[Day ticket] + dfs(nextDay)

costWeek = cost[Week ticket] + dfs(next day after a week)

costMonth = cost[Month ticket] + dfs(next dat after a month)

cost on day i, dp[i]= min(costDay, costWeek, costMonth)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution:
def mincostTickets(self, days: List[int], costs: List[int]) -> int:
memo = {}

def dfs(idx):
if idx >= len(days): return 0
if idx in memo:
return memo[idx]

costDay = costs[0] + dfs(idx+1)

# skip till ith day is curr_day+7 as we are buying week pass
i = idx
while i < len(days) and days[i] < days[idx]+7:
i += 1
costWeek = costs[1] + dfs(i)

# skip till ith day is curr_day+30 as we are buying month pass
i = idx
while i < len(days) and days[i] < days[idx]+30:
i += 1
costMonth = costs[2] + dfs(i)
memo[idx] = min(costDay, costWeek, costMonth)
return memo[idx]
return dfs(0)

Bottom up

We track the minimum cost for each travel day. We process only travel days and store {day, cost} for 7-and 30-day passes in the last7 and last30 queues. After a pass ‘expires’, we remove it from the queue. This way, our queues only contains travel days for the last 7 and 30 days, and the cheapest pass prices are in the front of the queues.

https://assets.leetcode.com/users/votrubac/image_1548617861.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def mincostTickets(self, days: List[int], costs: List[int]) -> int:
'''
two queues to store the ticket bought on day x
day7, day30
if a we travel on days[i], we could
1. buy one day pass
2. use 7 day pass in queue. We use FIFO queue, so might not be
3. use 30 day pass in queue
'''
queue7, queue30 = deque([]), deque([])
res = 0
for day in days:
while queue7 and queue7[0][0] + 7 <= day:
queue7.popleft()
while queue30 and queue30[0][0] + 30 <= day:
queue30.popleft()
# now two queues contains the available pass
queue7.append((day, res+costs[1]))
queue30.append((day, res+costs[2]))

res = min(res + costs[0], queue7[0][1], queue30[0][1])
return res

time complexity: $O()$
space complexity: $O()$
reference:
related problem: