题目描述
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。
火车票有三种不同的销售方式:
一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。
返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。
样例
输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。
输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。
你总共花了 $17,并完成了你计划的每一天旅行。
算法1
(动态规划) $O(30 * n)$
状态表示:
$f[i]$代表完成前i个旅游日的旅行后,所有支付方案的集合;
属性为最小值。
状态转移:
$f[i] = min(f[i], f[j - 1] + cost)$
j的含义是,完成前i个旅游日的旅行时,最后一次买票,是在第j个旅游日;
枚举j求最小值;
要注意,j必须在没过期的窗口里,比如最后一次在第j个旅游日购买了为期7天的票,那days[j]和days[i]相差不能超过6天;
转移方程的解释:在第j个旅游日买了票,那么买票前花销就是f[j - 1],加上这次的花销即可,求的是最小值,所以集合内取min。
时间复杂度
对于每一个需要旅行的日子,只需要探索它前面30天的窗口即可。
参考文献
https://www.acwing.com/solution/LeetCode/content/877/
C++ 代码
class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
int n = days.size();
int f[n + 1]; memset(f, 0x3f3f3f3f,sizeof f); f[0] = 0;
for (int i = 1; i <= n; ++i){
// 完成前i个旅游日时,最近的一次购票为第j个旅游日
for (int j = i; j >= 1; --j){
// 在第j个旅游日买了票,那么买票前花销就是f[j - 1],加上这次的花销即可
if (days[i - 1] - days[j - 1] < 1) f[i] = min(f[i], f[j - 1] + costs[0]);
if (days[i - 1] - days[j - 1] < 7) f[i] = min(f[i], f[j - 1] + costs[1]);
if (days[i - 1] - days[j - 1] < 30) f[i] = min(f[i], f[j - 1] + costs[2]);
if (days[i - 1] - days[j - 1] >= 30) break;
}
}
return f[n];
}
};
算法2
(单调队列?) $O(n)$
维护队列s,使得s队列的首元素,为最后一次所购票为7天票的最佳方案,在第day天购买了7天票,总共花销cost;
维护队列t,使得t队列的首元素,为最后一次所购票为30天票的最佳方案,在第day天购买了30天票,总共花销cost。
时间复杂度
参考文献
https://leetcode.com/problems/minimum-cost-for-tickets/discuss/226659/Two-DP-solutions-with-pictures
C++ 代码
class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
// 存入购票日和当前花销{day, cost}
queue<pair<int, int>> s, t;
int res = 0;
for (int d: days){
// 如果购票日距离当前旅游日太远,舍弃
while (!s.empty() && s.front().first + 7 <= d) s.pop();
while (!t.empty() && t.front().first + 30 <= d) t.pop();
// 购买7天票,30天票并计入队列
s.push({d, res + costs[1]});
t.push({d, res + costs[2]});
// 求完成当前旅游日的最小开销
res = min(res + costs[0], min(s.front().second, t.front().second));
}
return res;
}
};