JavaScript算法06-最低票价

一、问题描述

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

提示:
在这里插入图片描述

二、解题方法

看到题目首先思考的是贪心和动态规划两种方法,但是看到数组长度范围咋1-365,果断选择了动态规划。

2.1 动态规划

下面给出简要的解题思路:

  • 用一个dp数组来记录已完成旅行天数的最小花费,dp[i]代表日期days[0]~days[i-1]完成的最小花费钱数;
  • base case:days[0]之前都不存在旅游行为,花费为0,所以dp[0]=0;
  • 对于dp[i],对应包含days[i-1]这一天在内之前的时间段days[0]~days[i-1]均以完成旅行,那么之前的某一天j一定进行了决策:
    - 买入了1日票:花费为dp[j] + costs[0], 条件为days[i-1] - days[j] < 1;
    - 买入了7日票:花费为dp[j] + costs[1], 条件为days[i-1] - days[j] < 7;
    - 买入了30日票:花费为dp[j] + costs[2], 条件为days[i-1] - days[j] < 30;
  • dp[i]是上述情况中的最小值,最后返回结果dp[days.length]即可;

代码如下:

/**
 * @param {number[]} days
 * @param {number[]} costs
 * @return {number}
 */
var mincostTickets = function(days, costs) {
	let dp=days.slice().fill(0);
	dp.push(0);  //这里就是把dp[0]写入进去
	let [p1,p7,p30]=costs;
	for(let i=1;i<dp.length;i++){ //这里下标要从1开始,要是从0开始就会用infinity覆盖之前的dp[0]=0,永远找不到最小值
			let min=Infinity;
			for(let j=i-1;j>=0&&days[i-1]-days[j]<30;j--]){
				if(days[i-1]-days[j]<1)min=Math.min(dp[j]+p1,min);
				if(days[i-1]-days[j]<7)min=Math.min(dp[j]+p7,min);
				min=Math.min(dp[j]+p30,min);
			}
			dp[i]=min;
	}
	return dp[dp.length-1];
}

看题解的时候还发现另一个从后向前的倒序计算方法,其解题思路如下:

  • dp[i]设置为从第i天开始到最后一天的最小票价
  • 从最后一天开始往第一天循环,中间遇到不需要旅游,则当天花费=前一天花费,即dp[i]=dp[i+1]
  • 设置一个days的下标变量k,当i=days[k]时,则说明第i天需要旅游,则这时需要计算最小花费:dp[i]=Math.min(dp[i+1]+cost[0],dp[i+7]+cost[1],dp[i+30]+cost[2])。同时将k下标前移一位k–
  • 最后,i循环到1,算出最小票价,取days数组第一位dp[0]的花费即可。
/**
 * @param {number[]} days
 * @param {number[]} costs
 * @return {number}
 */
var mincostTickets = function(days, costs) {
	let dp=new Array(366+30).fill(0);
    n=days.length;
    maxDay=days[n-1];
    minDay=days[0];
    k=n-1;
    for(let i=maxDay;i>=minDay;i--){
        if(i===days[k]){
            dp[i]=Math.min(dp[i+1]+costs[0],dp[i+7]+costs[1],dp[i+30]+costs[2]);
            k--;}else{
            dp[i]=dp[i+1]
            }
        }
    return dp[minDay];
}

三、知识补充

javaScript中的无穷数(Infinity)

Infinity在JS中是一个特殊的数字,其特性为:比任何有限的数字都要大。可分为两种:正无穷和负无穷,JS中对应表达方式为:+Infinity(或者Infinity) 和 -Infinity。
当我们需要初始化涉及数字比较的计算时,无穷值就非常方便。例如,在数组中搜索最小值时:

function findMin(array) {
  let min = Infinity;
  for (const item of array) {
    min = Math.min(min, item);
  }
  return min;
}
findMin([5, 2, 1, 4]); // => 1

min变量使用Infinity初始化。在第一次for()迭代中,最小值成为第一项。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值