题目解析:徒步旅行中的补给问题 | 豆包MarsCode AI刷题

147 阅读5分钟

问题理解与分析

这道题目要求我们在给定的条件下,计算出小R完成徒步旅行的最低花费。具体来说,小R每天需要消耗1份食物,且每天经过的补给站的食物价格可能不同。小R最多只能携带 K 份食物,这意味着我们需要在每天的决策中考虑如何购买食物以最小化总花费。

动态规划思路

为了解决这个问题,我们可以使用动态规划(Dynamic Programming, DP)的思想。动态规划的核心思想是将问题分解为子问题,并通过解决子问题来解决原问题。在这个问题中,我们可以定义一个状态 dp[i][t],表示在第 i 天结束时,小R携带 t 份食物时的最小花费。

状态转移方程

我们可以通过以下几种情况来考虑状态转移:

  1. 不购买食物:如果小R在第 i 天不购买食物,那么他需要在前一天已经携带了足够的食物。此时,状态转移方程为:

    dp[i][t]=dp[i−1][t+1]dp[i][t]=dp[i−1][t+1]

    其中 t+1 表示前一天结束时小R需要携带 t+1 份食物。

  2. 购买食物:如果小R在第 i 天购买食物,那么他需要考虑购买多少份食物。假设购买 j 份食物,那么状态转移方程为:

    dp[i][t]=min⁡(dp[i][t],dp[i−1][t+1−j]+j×data[i])

    其中 t+1-j 表示前一天结束时小R需要携带 t+1-j 份食物,j \times \text{data}[i] 表示购买 j 份食物的花费。

边界条件

在初始状态下,小R在第0天结束时没有食物,因此 dp[0][0] = 0,其他状态初始化为无穷大(表示不可达状态)。

记忆化搜索

在代码实现中,我们使用了一个递归函数 dfs(i, t) 来计算 dp[i][t]。递归函数的终止条件是当 i >= n 时,表示旅行已经结束,此时返回0。在递归过程中,我们考虑了不购买食物和购买食物两种情况,并通过 min 函数来选择最小的花费。

def solution(n, k, data):
    dp = [[-1]*(k+1) for i in range(n)]
    
    def dfs(i, t):
        if i >= n:
            return 0
        if dp[i][t] != -1:
            return dp[i][t]
        cost = float('inf')
        if t >= 1:
            cost = min(dfs(i+1, t-1), cost)
        for j in range(1, k-t+1):
            cost = min(dfs(i+1, t-1+j) + j*data[i], cost)
        dp[i][t] = cost
        return cost
    
    ans = dfs(0, 0)
    return ans

复杂度分析

  • 时间复杂度:由于我们使用了递归和动态规划,时间复杂度为 O(n * k^2),其中 n 是天数,k 是小R最多能携带的食物份数。对于每个状态 dp[i][t],我们需要考虑 k 种购买食物的情况,因此总的时间复杂度为 O(n * k^2)
  • 空间复杂度:我们使用了一个二维数组 dp 来存储状态,因此空间复杂度为 O(n * k)

非递归实现

def solution(n, k, data):
    # 初始化dp数组,dp[i][t]表示在第i天结束时,携带t份食物的最小花费
    dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
    
    # 初始状态:第0天结束时,携带0份食物的花费为0
    dp[0][0] = 0
    
    # 遍历每一天
    for i in range(1, n + 1):
        # 遍历前一天结束时携带的食物份数
        for t in range(k + 1):
            # 如果前一天结束时携带的食物份数为t,今天不购买食物
            if t >= 1:
                dp[i][t - 1] = min(dp[i][t - 1], dp[i - 1][t])
            
            # 购买食物的情况
            for j in range(1, k - t + 1):
                dp[i][t + j - 1] = min(dp[i][t + j - 1], dp[i - 1][t] + j * data[i - 1])
    
    # 最终答案是第n天结束时,携带0份食物的最小花费
    return dp[n][0]
  1. 初始化dp数组

    • dp 是一个二维数组,dp[i][t] 表示在第 i 天结束时,小R携带 t 份食物时的最小花费。初始值为无穷大(float('inf')),表示该状态还未计算。
    • 初始状态:第 0 天结束时,携带 0 份食物的花费为 0,即 dp[0][0] = 0
  2. 状态转移

    • 遍历每一天 i(从 1 到 n)。

    • 遍历前一天结束时携带的食物份数 t(从 0 到 k)。

    • 不购买食物:如果前一天结束时携带的食物份数 t 大于等于 1,表示今天不购买食物,直接转移到下一天。状态转移方程为: dp[i][t−1]=min⁡(dp[i][t−1],dp[i−1][t])

    • 购买食物:对于 j 从 1 到 k - t,表示购买 j 份食物,计算相应的花费,并更新 dp[i][t + j - 1]。状态转移方程为:dp[i][t+j−1]=min⁡(dp[i][t+j−1],dp[i−1][t]+j×data[i−1])

  3. 最终答案

    • 最终答案是第 n 天结束时,携带 0 份食物的最小花费,即 dp[n][0]

总结

通过直接使用动态规划,我们避免了递归调用的开销,并且代码更加简洁和易于理解。动态规划的核心思想是通过状态转移方程来逐步计算出最终答案。在这个问题中,我们通过遍历每一天和每种可能的食物携带量,逐步更新 dp 数组,最终得到了最小花费。