题目描述
在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days
的数组给出。每一项是一个从 1
到 365
的整数。
火车票有 三种不同的销售方式 :
- 一张 为期一天 的通行证售价为
costs[0]
美元;
- 一张 为期七天 的通行证售价为
costs[1]
美元;
- 一张 为期三十天 的通行证售价为
costs[2]
美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2
天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第 2
天、第 3
天、第 4
天、第 5
天、第 6
天、第 7
天和第 8
天。
返回 你想要完成在给定的列表 days
中列出的每一天的旅行所需要的最低消费 。
示例 1:
输入: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,并完成了你计划的每一天旅行。
示例 2:
输入: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 <= days.length <= 365
1 <= days[i] <= 365
days
按顺序严格递增
costs.length == 3
1 <= costs[i] <= 1000
解法
方法一:记忆化搜索 + 二分查找
我们定义一个函数 $\textit{dfs(i)}$,表示从第 $i$ 次出行开始到最后一次出行结束所需的最小花费。那么答案为 $\textit{dfs(0)}$。
函数 $\textit{dfs(i)}$ 的执行过程如下:
- 如果 $i \geq n$,表示所有出行已经结束,返回 $0$;
- 否则,我们需要考虑三种购买方式,分别是购买 $1$ 天通行证、购买 $7$ 天通行证和购买 $30$ 天通行证。我们分别计算这三种购买方式的花费,并且利用二分查找,找到下一次出行的下标 $j$,然后递归调用 $\textit{dfs(j)}$,最后返回这三种购买方式的最小花费。
为了避免重复计算,我们使用记忆化搜索,将已经计算过的结果保存起来。
时间复杂度 $O(n \times \log n)$,空间复杂度 $O(n)$。其中 $n$ 表示出行的次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | class Solution:
def mincostTickets(self, days: List[int], costs: List[int]) -> int:
@cache
def dfs(i: int) -> int:
if i >= n:
return 0
ans = inf
for c, v in zip(costs, valid):
j = bisect_left(days, days[i] + v)
ans = min(ans, c + dfs(j))
return ans
n = len(days)
valid = [1, 7, 30]
return dfs(0)
|
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
26
27
28
29
30
31 | class Solution {
private final int[] valid = {1, 7, 30};
private int[] days;
private int[] costs;
private Integer[] f;
private int n;
public int mincostTickets(int[] days, int[] costs) {
n = days.length;
f = new Integer[n];
this.days = days;
this.costs = costs;
return dfs(0);
}
private int dfs(int i) {
if (i >= n) {
return 0;
}
if (f[i] != null) {
return f[i];
}
f[i] = Integer.MAX_VALUE;
for (int k = 0; k < 3; ++k) {
int j = Arrays.binarySearch(days, days[i] + valid[k]);
j = j < 0 ? -j - 1 : j;
f[i] = Math.min(f[i], dfs(j) + costs[k]);
}
return f[i];
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
int valid[3] = {1, 7, 30};
int n = days.size();
int f[n];
memset(f, 0, sizeof(f));
function<int(int)> dfs = [&](int i) {
if (i >= n) {
return 0;
}
if (f[i]) {
return f[i];
}
f[i] = INT_MAX;
for (int k = 0; k < 3; ++k) {
int j = lower_bound(days.begin(), days.end(), days[i] + valid[k]) - days.begin();
f[i] = min(f[i], dfs(j) + costs[k]);
}
return f[i];
};
return dfs(0);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | func mincostTickets(days []int, costs []int) int {
valid := [3]int{1, 7, 30}
n := len(days)
f := make([]int, n)
var dfs func(int) int
dfs = func(i int) int {
if i >= n {
return 0
}
if f[i] > 0 {
return f[i]
}
f[i] = 1 << 30
for k := 0; k < 3; k++ {
j := sort.SearchInts(days, days[i]+valid[k])
f[i] = min(f[i], dfs(j)+costs[k])
}
return f[i]
}
return dfs(0)
}
|
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
26
27
28
29
30
31
32 | function mincostTickets(days: number[], costs: number[]): number {
const n = days.length;
const f: number[] = Array(n).fill(0);
const valid: number[] = [1, 7, 30];
const search = (x: number): number => {
let [l, r] = [0, n];
while (l < r) {
const mid = (l + r) >> 1;
if (days[mid] >= x) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
};
const dfs = (i: number): number => {
if (i >= n) {
return 0;
}
if (f[i]) {
return f[i];
}
f[i] = Infinity;
for (let k = 0; k < 3; ++k) {
const j = search(days[i] + valid[k]);
f[i] = Math.min(f[i], dfs(j) + costs[k]);
}
return f[i];
};
return dfs(0);
}
|
方法二:动态规划
我们不妨记 $\textit{days}$ 数组中的最后一天为 $m$,那么我们可以定义一个长度为 $m + 1$ 的数组 $f$,其中 $f[i]$ 表示从第 $1$ 天到第 $i$ 天的最小花费。
我们可以按照 $\textit{days}$ 数组中的日期递增的顺序,从第 $1$ 天开始,依次计算 $f[i]$ 的值。如果第 $i$ 天是出行的日期,那么我们可以考虑三种购买方式,分别是购买 $1$ 天通行证、购买 $7$ 天通行证和购买 $30$ 天通行证。我们分别计算这三种购买方式的花费,并且取这三种购买方式的最小花费作为 $f[i]$ 的值。如果第 $i$ 天不是出行的日期,那么 $f[i] = f[i - 1]$。
最终答案为 $f[m]$。
时间复杂度 $O(m)$,空间复杂度 $O(m)$。其中 $m$ 表示出行的最后一天。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | class Solution:
def mincostTickets(self, days: List[int], costs: List[int]) -> int:
m = days[-1]
f = [0] * (m + 1)
valid = [1, 7, 30]
j = 0
for i in range(1, m + 1):
if i == days[j]:
f[i] = inf
for c, v in zip(costs, valid):
f[i] = min(f[i], f[max(0, i - v)] + c)
j += 1
else:
f[i] = f[i - 1]
return f[m]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | class Solution {
public int mincostTickets(int[] days, int[] costs) {
int m = days[days.length - 1];
int[] f = new int[m + 1];
final int[] valid = {1, 7, 30};
for (int i = 1, j = 0; i <= m; ++i) {
if (i == days[j]) {
f[i] = Integer.MAX_VALUE;
for (int k = 0; k < 3; ++k) {
int c = costs[k], v = valid[k];
f[i] = Math.min(f[i], f[Math.max(0, i - v)] + c);
}
++j;
} else {
f[i] = f[i - 1];
}
}
return f[m];
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | class Solution {
public:
int mincostTickets(vector<int>& days, vector<int>& costs) {
int m = days.back();
int f[m + 1];
f[0] = 0;
int valid[3] = {1, 7, 30};
for (int i = 1, j = 0; i <= m; ++i) {
if (i == days[j]) {
f[i] = INT_MAX;
for (int k = 0; k < 3; ++k) {
int c = costs[k], v = valid[k];
f[i] = min(f[i], f[max(0, i - v)] + c);
}
++j;
} else {
f[i] = f[i - 1];
}
}
return f[m];
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | func mincostTickets(days []int, costs []int) int {
m := days[len(days)-1]
f := make([]int, m+1)
valid := [3]int{1, 7, 30}
for i, j := 1, 0; i <= m; i++ {
if i == days[j] {
f[i] = 1 << 30
for k, v := range valid {
c := costs[k]
f[i] = min(f[i], f[max(0, i-v)]+c)
}
j++
} else {
f[i] = f[i-1]
}
}
return f[m]
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | function mincostTickets(days: number[], costs: number[]): number {
const m = days.at(-1)!;
const f: number[] = Array(m).fill(0);
const valid: number[] = [1, 7, 30];
for (let i = 1, j = 0; i <= m; ++i) {
if (i === days[j]) {
f[i] = Infinity;
for (let k = 0; k < 3; ++k) {
const [c, v] = [costs[k], valid[k]];
f[i] = Math.min(f[i], f[Math.max(0, i - v)] + c);
}
++j;
} else {
f[i] = f[i - 1];
}
}
return f[m];
}
|