补给站的最优花费问题 | 豆包MarsCode AI 刷题

49 阅读5分钟

问题描述

小U计划进行一场从地点A到地点B的徒步旅行,旅行总共需要 M 天。为了在旅途中确保安全,小U每天都需要消耗一份食物。在路程中,小U会经过一些补给站,这些补给站分布在不同的天数上,且每个补给站的食物价格各不相同。

小U需要在这些补给站中购买食物,以确保每天都有足够的食物。现在她想知道,如何规划在不同补给站的购买策略,以使她能够花费最少的钱顺利完成这次旅行。

  • M:总路程所需的天数。
  • N:路上补给站的数量。
  • p:每个补给站的描述,包含两个数字 A 和 B,表示第 A 天有一个补给站,并且该站每份食物的价格为 B 元。

保证第0天一定有一个补给站,并且补给站是按顺序出现的。


测试样例

样例1:

输入:m = 5 ,n = 4 ,p = [[0, 2], [1, 3], [2, 1], [3, 2]]
输出:7

样例2:

输入:m = 6 ,n = 5 ,p = [[0, 1], [1, 5], [2, 2], [3, 4], [5, 1]]
输出:6

样例3:

输入:m = 4 ,n = 3 ,p = [[0, 3], [2, 2], [3, 1]]
输出:9


def solution(m: int, n: int, p: list[list[int]]) -> int:
    # 初始化dp数组,dp[i]表示在第i天结束时的最小花费
    dp = [float('inf')] * (m + 1)
    dp[0] = 0  # 第0天的最小花费为0

    # 遍历每一天
    for i in range(1, m + 1):
        # 遍历之前的每一天
        for j in range(i):
            # 找到第j天是否有补给站
            for station in p:
                if station[0] == j:
                    price = station[1]
                    # 更新dp[i],考虑从第j天购买食物到第i天
                    dp[i] = min(dp[i], dp[j] + price * (i - j))
                    break

    # 返回第m天的最小花费
    return dp[m]

if __name__ == "__main__":
    # Add your test cases here
    print(solution(5, 4, [[0, 2], [1, 3], [2, 1], [3, 2]]) == 7)

这段代码实现了一个动态规划算法,用于解决旅行中在补给站购买食物的最小花费问题。动态规划是一种在数学、管理科学、计算机科学、经济学和生物信息学等领域中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

学习心得:

  1. 动态规划的应用场景: 动态规划通常用于解决具有重叠子问题和最优子结构特性的问题。在这个问题中,每天的最小花费依赖于之前天数的最小花费,这构成了重叠子问题。同时,每天的最小花费可以通过前一天的最小花费计算得出,这体现了最优子结构。
  2. 初始化和边界条件: 在动态规划中,正确的初始化和边界条件的设置至关重要。在这段代码中,dp[0] 被初始化为 0,因为第 0 天已经在补给站,所以没有额外花费。这是动态规划中常见的起点设置。
  3. 状态转移方程: 动态规划的核心是状态转移方程。在这个问题中,状态转移方程是 dp[i] = min(dp[i], dp[j] + price * (i - j)),它表示第 i 天的最小花费可以通过考虑从第 j 天购买食物到第 i 天来更新。这个方程体现了动态规划的思想,即通过之前的最优解来构建当前的最优解。
  4. 循环结构: 代码中的三层循环结构分别遍历天数、前一天的天数和补给站,这种结构有助于理解如何逐步构建解。外层循环遍历每一天,中间循环遍历之前的所有天,内层循环遍历补给站,以找到对应天数的补给站和价格。
  5. 优化空间复杂度: 虽然代码能够解决问题,但在实际应用中,我们可能需要考虑优化空间复杂度。例如,由于每天只需要考虑前一天的信息,我们可以只使用一个一维数组来存储前一天的最小花费,从而将空间复杂度从 O(M) 降低到 O(1)。
  6. 测试用例的重要性: 测试用例是验证算法正确性的关键。在代码的最后,作者提供了几个测试用例来确保算法在不同情况下都能正确工作。这是软件开发中的一个重要步骤,有助于发现和修复潜在的错误。
  7. 代码的可读性和维护性: 代码的可读性和维护性对于理解和维护代码非常重要。在这段代码中,虽然逻辑清晰,但可以通过添加注释和使用更直观的变量名来进一步提高代码的可读性。
  8. 算法的局限性和改进: 在实际应用中,我们可能需要考虑算法的局限性和可能的改进。例如,如果补给站的价格随时间变化,或者补给站提供的食物数量有限,那么算法需要进行相应的调整。

通过这段代码,我们不仅学习了如何使用动态规划解决实际问题,还学习了如何设计和实现算法、如何测试算法的正确性以及如何优化算法的性能。这些都是计算机科学和软件开发中非常重要的技能。