983、最低票价

  1. 最低票价
    1. 示例 1:
    2. 示例 2:
  2. 题解
    1. 1、记忆化搜索——日期变量型
    2. 2、记忆化搜索——窗口变量型

最低票价

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

链接:https://leetcode-cn.com/problems/minimum-cost-for-tickets

题解

1、记忆化搜索——日期变量型

自顶向下,dp[i]表示从第天开始到一年的结束,我们需要花的费用。

对于一年内的任意一天i:

  • dp[i] = dp[i + 1], i不需要出行
  • `dp[i] = min(cost(j) + dp[i+j]), i必须出行,cost(j)表示j天处行政的价格
class Solution {
    int[] costs;
    // 用包装类数组,方便知道是否有数据
    Integer[] memo;
    Set<Integer> daySet;

    public int minCostTickets(int[] days,int[] costs){
        this.costs = costs;
        memo = new Integer[366];
        daySet = new HashSet<Integer>();
        for (int d : days){
            daySet.add(d);
        }

        return dp(1);
    }

    /*
        自顶向下记忆化搜索递归函数,
    */
    private int dp(int i) {
        // 超过365天
        if (i > 365) {
            return 0;
        }
        // 第i天已经记录了
        if (memo[i] != null) {
            return memo[i];
        }

        if (daySet.contains(i)) { // 必须旅行
            memo[i] = Math.min(Math.min(dp(i + 1) + costs[0],dp(i+7) + costs[1]),dp(i + 30) + costs[2]);
        } else { // 不需要旅行
            memo[i] = dp(i+1);
        }

        return memo[i];
    }
}

2、记忆化搜索——窗口变量型

dp[i] 表示days[i]到当下通信证的最后一天的最小花费。

令 $j_1$ 是满足 $\textit{days}[j_1] >= \textit{days}[i] + 1$ 的最小下标,$j_7$ 是满足 $\textit{days}[j_7] >= \textit{days}[i] + 7$ 的最小下标,$j_{30}$ 是满足 $\textit{days}[j_{30}] >= \textit{days}[i] + 30$ 的最小下标,那么就有:
$$
dp(i) = min(dp(j_1)+ costs[0],dp(j_7) + costs[1],dp(j_{30}+costs[2))
$$

class Solution {
    int[] days,costs;
    Integer[] memo;
    // 通行证有效时间
    int[] durations = new int[]{1,7,30};
    public int  minCostTickets(int[] days,int[] costs) {
        this.days = days;
        this.costs = costs;
        memo = new Integer[days.length];
        return dp(0);
    }

    private int dp(int i) {
        if (i >= days.length) {
            return 0;
        }

        if (memo[i] != null) {
            return memo[i];
        }

        memo[i] = Integer.MAX_VALUE;
        int j = i;
        for (int k = 0;k < 3;++k) {
            // 没有超过目标天数 且 找最小的j,其超过了通行证的有效时间
            while(j < days.length && days[j] < days[i] + durations[k]) {
                j++;
            }
            memo[i] = Math.min(memo[i],dp(j)+costs[k]);
        }

        return memo[i];
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1056615746@qq.com

💰

Title:983、最低票价

Count:1.1k

Author:攀登

Created At:2020-07-26, 00:19:44

Updated At:2024-06-15, 15:52:32

Url:http://jiafeimao-gjf.github.io/2020/07/26/983%E3%80%81%E6%9C%80%E4%BD%8E%E7%A5%A8%E4%BB%B7/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.

×

Help us with donation