You are given an integer array days
where each element represents a day you will travel, and an integer array costs
of length 3 where:
costs[0]
is the cost of a 1-day pass,costs[1]
is the cost of a 7-day pass,costs[2]
is the cost of a 30-day pass.days
with at least one pass. Your task is to find the minimum total cost required to travel on all the days in days
.
Constraints:
days.length
≤ 365days[i]
≤ 365, with days
strictly increasingcosts[i]
≤ 1000At first glance, the problem seems to invite a brute-force approach: for each travel day, try every possible combination of ticket purchases and find the minimum cost. However, this would be highly inefficient, as the number of combinations grows exponentially with the number of days.
To optimize, we notice that for each travel day, our decision is local: on that day, we can buy a 1-day, 7-day, or 30-day pass. Each choice affects which future days are covered. This is a classic case for dynamic programming, where we can build up the solution by solving smaller subproblems and reusing their results.
The key insight is that the minimum cost to cover all days up to a certain day can be computed from the minimum cost to cover previous days, based on which pass we buy at each step.
We use dynamic programming to solve the problem efficiently. Here is a step-by-step breakdown:
dp[i]
represent the minimum cost to cover all travel days from days[i]
to the end.i
, we have three choices:
costs[0] + dp[next_i_1]
where next_i_1
is the first day after days[i]
.costs[1] + dp[next_i_7]
where next_i_7
is the first day after days[i] + 6
.costs[2] + dp[next_i_30]
where next_i_30
is the first day after days[i] + 29
.days
in reverse (from last to first), filling in the dp
array.i
is beyond the last index, where the cost is 0.Sample Input:
days = [1,4,6,7,8,20]
costs = [2,7,15]
Let's walk through the solution:
Brute-force:
The optimized approach is efficient even for the maximum constraints.
The Minimum Cost For Tickets problem is a classic example of optimizing local decisions using dynamic programming. By framing the problem as a series of overlapping subproblems—each representing the minimum cost from a certain day onward—we avoid redundant computation and efficiently find the optimal solution. The elegance lies in the simplicity of the recurrence and the efficient use of data structures to store intermediate results.
class Solution:
def mincostTickets(self, days, costs):
n = len(days)
dp = [0] * (n + 1)
durations = [1, 7, 30]
for i in range(n - 1, -1, -1):
dp[i] = float('inf')
for k in range(3):
j = i
# Find the next index not covered by this pass
while j < n and days[j] < days[i] + durations[k]:
j += 1
dp[i] = min(dp[i], costs[k] + dp[j])
return dp[0]
class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
int n = days.size();
vector<int> dp(n + 1, 0);
vector<int> durations = {1, 7, 30};
for (int i = n - 1; i >= 0; --i) {
dp[i] = INT_MAX;
for (int k = 0; k < 3; ++k) {
int j = i;
while (j < n && days[j] < days[i] + durations[k]) {
++j;
}
dp[i] = min(dp[i], costs[k] + dp[j]);
}
}
return dp[0];
}
};
class Solution {
public int mincostTickets(int[] days, int[] costs) {
int n = days.length;
int[] dp = new int[n + 1];
int[] durations = {1, 7, 30};
for (int i = n - 1; i >= 0; --i) {
dp[i] = Integer.MAX_VALUE;
for (int k = 0; k < 3; ++k) {
int j = i;
while (j < n && days[j] < days[i] + durations[k]) {
j++;
}
dp[i] = Math.min(dp[i], costs[k] + dp[j]);
}
}
return dp[0];
}
}
var mincostTickets = function(days, costs) {
const n = days.length;
const dp = new Array(n + 1).fill(0);
const durations = [1, 7, 30];
for (let i = n - 1; i >= 0; --i) {
dp[i] = Infinity;
for (let k = 0; k < 3; ++k) {
let j = i;
while (j < n && days[j] < days[i] + durations[k]) {
j++;
}
dp[i] = Math.min(dp[i], costs[k] + dp[j]);
}
}
return dp[0];
};