问题背景
小R正在进行一次从地点A到地点B的徒步旅行,总共需要 N 天。为了在旅途中保持充足的能量,小R每天必须消耗 1 份食物。途中每天都会经过一个补给站,每个补给站的食物价格不同。此外,小R最多只能携带 K 份食物。因此,为了完成旅行,小R需要在保证每天都有食物的情况下,以最小的花费完成这次旅程。
核心问题
-
约束条件:
- 每天消耗 1 份食物。
- 背包最多容纳 K 份食物。
-
目标:
- 通过合理的购买策略,最小化购买食物的花费。
输入输出描述
-
输入:
n: 总旅行天数。k: 食物背包最大容量。data: 长度为n的数组,每个元素代表某天补给站的单价。
-
输出:
- 完成整个旅程的最小花费。
示例
样例 1:
- 输入:
n = 5, k = 2, data = [1, 2, 3, 3, 2] - 输出:
9
样例 2:
- 输入:
n = 6, k = 3, data = [4, 1, 5, 2, 1, 3] - 输出:
9
样例 3:
- 输入:
n = 4, k = 1, data = [3, 2, 4, 1] - 输出:
10
解题思路
1. 动态规划建模
这个问题可以用动态规划来解决。我们用一个二维数组 dp[i][j] 表示以下含义:
i: 第i天。j: 到第i天结束时背包里剩余的食物数量。dp[i][j]: 表示从第 1 天到第i天,且在第i天背包剩余j份食物时的最小花费。
状态转移方程
假设在第 i-1 天背包中剩余了 l 份食物,现在要转移到第 i 天背包剩余 j 份食物的状态:
- 需要购买的食物量为
j - l + 1(因为每天消耗 1 份食物)。 - 第
i天的总花费为: dp[i][j]=min(dp[i−1][l]+(j−l+1)×data[i−1])dp[i][j] = \min(dp[i-1][l] + (j - l + 1) \times data[i-1])dp[i][j]=min(dp[i−1][l]+(j−l+1)×data[i−1]) 其中,j - l + 1需要满足: 0≤j−l+1≤k0 \leq j - l + 1 \leq k0≤j−l+1≤k
边界条件
- 第 0 天:
dp[0][0] = 0(出发时没有花费)。 dp[0][j](其他初始值):设为无穷大,因为不可能在出发时剩余食物。
最终答案
最终在第 n 天,dp[n][0] 即为小R完成旅行的最小花费。
2. 算法实现
以下是完整代码的实现:
python
复制代码
def solution(n, k, data):
# 初始化动态规划数组
dp = [[float('inf')] * (k + 1) for _ in range(n + 1)]
dp[0][0] = 0 # 初始条件:第 0 天无花费
# 状态转移
for i in range(1, n + 1): # 遍历每一天
for l in range(k): # 前一天剩余的食物数
for j in range(k): # 今天剩余的食物数
if 0 <= j - l + 1 <= k: # 判断购买是否合法
dp[i][j] = min(dp[i][j], dp[i - 1][l] + (j - l + 1) * data[i - 1])
# 返回答案:第 n 天,背包空的最小花费
return dp[n][0]
# 测试样例
if __name__ == "__main__":
print(solution(5, 2, [1, 2, 3, 3, 2]) == 9)
print(solution(6, 3, [4, 1, 5, 2, 1, 3]) == 9)
print(solution(4, 1, [3, 2, 4, 1]) == 10)
思路分析与总结
时间复杂度
动态规划的三重循环结构:
O(n)遍历天数。O(k)遍历前一天的食物剩余量。O(k)遍历当天的食物剩余量。
总体时间复杂度为 O(n * k^2)。
空间复杂度
使用了一个二维数组 dp,空间复杂度为 O(n * k)。
代码优化思考
目前的实现效率适中,但在 k 值较大时可能会造成性能瓶颈:
- 状态转移优化:可以使用滚动数组优化空间复杂度,从
O(n * k)降低到O(k)。 - 单调队列优化:在循环中,可以通过单调队列加速对最优状态的转移,进一步降低时间复杂度。