一、问题描述
小U计划进行一场从地点A到地点B的徒步旅行,总共需要 M 天。在这 M 天的行程中,小U每天需要消耗一份食物,而在路途中会遇到多个补给站。每个补给站提供食物,且不同的补给站在不同的天数提供食物,且价格各不相同。小U的目标是通过在合适的补给站购买食物来最小化旅行中的花费,同时确保每天有足够的食物。
1.输入:
- M:旅行总天数。
- N:补给站的数量。
- p:一个二维数组,每个元素
[A, B]表示第 A 天有一个补给站,且该站每份食物的价格为 B 元。 保证第 0 天一定有一个补给站,并且补给站是按天数顺序出现的,也就是说p这个二维数组是按照[A,B]里面的A升序排列的。
2.输出:即为最小花费,表示小U完成旅行所需的最少花费。
二、动态规划的思路
因为问题中涉及到 最小成本 和 状态转移,并且有多个子问题可以进行重复利用,所以我们选择用动态规划来解决问题。我们可以由小到大构建出从起点到终点的最优解,这正是动态规划非常经典的应用场景。
我们可以用一个数组 minCost 来表示从第0天到第i天的最小花费。通过逐步更新 minCost 数组,最终得到从起点到终点所需的最小花费。
1. 状态定义
先定义一个数组 minCost,其中 minCost[i] 表示从第 0 天到第 i 天所需要的最小花费。
- 初始状态:
minCost[0] = 0,起点时花费为0。 - 其余的天数:
minCost[i]初始值为Infi,表示我们还没有找到到达该天的最小花费。
2. 状态转移
对于每个补给站 p[i] = [day, price],我们尝试从这个补给站出发,并更新从第 day 天到后续天数的最小花费。假设我们从 day 天出发并且在该站购买食物,价格为 price,那么从 day 到 j 天的花费为 (j - day) * price,因此可以更新数组minCost的值:
minCost[j] = Math.min(minCost[j], minCost[day] + (j - day) * price);
这里的意思是:从 day 天到第 j 天,我们会累积 (j - day) * price 的花费,更新 minCost[j] 为可能的最小值。
通过上述的定义的状态转移规则,最终 minCost[m] 将会是计算出来的完成旅行的最小花费。
三、代码实现
下面是代码的详细实现:
function solution(m, n, p) {
const minCost = new Array(m + 1).fill(Infi);
minCost[0] = 0;
// 遍历每个补给站
for (let i = 0; i < n; i++) {
const [day, price] = p[i];
// 对于从补给站 day 到每一后续的天数 j,更新最小花费
for (let j = day; j <= m; j++) {
// 从补给站 day 到第 j 天的最小花费
minCost[j] = Math.min(minCost[j], minCost[day] + (j - day) * price);
}
}
return minCost[m];
}
代码解析
-
初始化:我们创建了一个长度为
m + 1的数组minCost,并将所有的元素初始化为Infi,代表默认情况下每一天的最小花费是无穷大。minCost[0] = 0表示起点不需要花费额外的钱。 -
遍历补给站:通过遍历每个补给站
[day, price],我们尝试从该补给站出发,更新后续天数的最小花费。 -
状态转移:对于每个补给站,从
day天到后续天数j,我们更新minCost[j],确保每一天的最小花费是最优的。 -
返回结果:最终,我们返回计算结果
minCost[m]、,它表示从第 0 天到第 m 天所需的最小花费。
四、复杂度分析
-
时间复杂度:假设补给站的数量为
N,总天数为M。遍历每个补给站后,我们更新每个后续天数的最小花费。因此,时间复杂度为O(N * M)。 -
空间复杂度:我们需要一个长度为
M + 1的数组minCost来存储最小花费,因此空间复杂度为O(M)。
五、总结
本题我们学习了如何使用动态规划来解决最小花费路径的问题。在这道题中,关键的动态规划思想是定义一个数组 minCost 来存储从起点到每个天数的最小花费,并通过遍历每个补给站来更新后续天数的最小花费。这个方法不仅帮助我们优化了旅行中的花费问题,还加深了我们对动态规划思想的理解和应用。虽然这道题在动态规划里面算是简单题的一种,但是我们正是要由小见大,由易到难,不断的深入理解和学习动态规划算法,才能为后续深入的学习打下更坚实的基础。