最低票价

2024/10/4 力扣每日一题动态规划

题目链接

难度:中等

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 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
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

今天虽然是十月四号了,不过今天的每日一题是一道纯数学题,虽然蒙对了答案,但是感觉没有写的必要,就来补一下国庆当天的题目。

看完题意知道是动态规划,还以为是比较简单的那种,没想到确实是比较简单的,只不过我做复杂了,拉了一坨大的。

function mincostTickets(days: number[], costs: number[]): number {
    const len = 365+2
    const dp = new Array(len).fill(0)
    const s = new Set(days)
    for(let i = 1; i < len; i++) {
        if (s.has(i)) {
            if (i < 7) {
                dp[i] = dp[i-1] + costs[0]
            } else if (i < 30) {
                dp[i] = Math.min(dp[i-1]+costs[0], dp[i-7]+costs[1], findMin(dp, 7, i)+costs[1])
            } else {
                dp[i] = Math.min(dp[i-1]+costs[0], dp[i-7]+costs[1], findMin(dp, 7, i)+costs[1],
                    dp[i-30]+costs[2], findMin(dp, 30, i) + costs[2])
            }
        } else {
            // 由于这边是不去旅游的那天,不需要额外的消费,所以 findMin 函数第二个参数需要天数加一
            if(i >= 30) {
                dp[i] = Math.min(dp[i-1], findMin(dp, 31, i) + costs[2], findMin(dp, 8, i) + costs[1])
            } else if (i >= 7) {
                dp[i] = Math.min(dp[i-1], findMin(dp, 8, i) + costs[1])
            } else {
                dp[i] = dp[i-1]
            }
        }
    }
    return dp[len-1]
};

// 找到dp[cnt]之前连续days天内的最低消费
const findMin = (arr, days, cnt) => {
    let ans = 366000
    for(let i = cnt - 1; i > cnt - days; i--) {
        ans = Math.min(arr[i], ans)
    }
    return ans
}
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
33
34
35
36

拿下!虽然解决了但却花了一个半小时,并且都是根据错误结果的提示来修改的,对动态规划还是不太熟悉。

看了官方题解也挺麻烦的,反倒在评论区看了一位老哥的题解,很好理解:

function mincostTickets(days: number[], costs: number[]): number {
    const n = 365+2
    const len = days.length
    const dp = new Array(len).fill(0)
    for(let i = 1, j = 0; i < n && j < len; i++) {
      dp[i] = dp[i - 1]
      if (i == days[j]) {
        dp[i] = Math.min(dp[Math.max(0, i - 1)]+costs[0], dp[Math.max(0, i - 7)]+costs[1], dp[Math.max(0, i - 30)]+costs[2])
        j++
      }
    }
    return dp[days[len - 1]]
};
1
2
3
4
5
6
7
8
9
10
11
12
13

看着思路我和他的差不多啊,为什么我写的这么复杂呢?

仔细对比了一下,我不应该把天数7、30的区间分隔开来计算,分开的结果就是和我一样写的很复杂。而且还多了不必要的计算,findMin函数其实是不需要的,他的作用其实是为了找到dp[0]。本来是想改良一下的,改着改着已经和上面差不多了,干脆放弃了。

时间复杂度:O(W),其中 W 是旅行计划中日期的最大值,我们需要计算 W 个解,而每个解最多需要查询 3 个其他的解,因此计算量为 O(3∗W)=O(W)。

Last Updated: 2024/10/15 15:29:12