问题描述
小R正在计划一次从地点A到地点B的徒步旅行,总路程需要 N天。为了在旅途中保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R在路途中每天都会经过一个补给站,可以先购买完食物后再消耗今天的1份食物。然而,每个补给站的食物每份的价格可能不同,并且小R在购买完食物后最多只能同时携带 K 份食物。
现在,小R希望在保证每天食物消耗的前提下,以最小的花费完成这次徒步旅行。你能帮助小R计算出最低的花费是多少吗?
输入
• n 总路程需要的天数
• k 小R最多能同时携带食物的份数
• data[i] 第i天补给站每份食物的价格
输出
• 返回完成这次徒步旅行的最小花费
约束条件
• 1 < n,k < 1000
• 1 < data[i] < 10000
测试样例
样例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
思路
刚开始本来想用动态规划来解决,但是写出来只能满足实例给的几个结果后面在网上找到的都是c++和Java解决的,最后我采用的是滑动窗口结合双端队列来解决的。
我先给出错误的那个代码
def solution(n, k, data):
current_food = 0
total_cost = 0
for i in range(n):
# 如果当前食物不足,需要购买
if current_food == 0 or data[i] == min(data[i:i+k]):
# 计算需要购买的食物量
buy_amount = min( k - current_food,n-i) # 最多购买 k 份,或者剩余天数
# 检查当前价格是否是接下来 k 天中最便宜的
if data[i] == min(data[i:i+buy_amount]):
# 如果是,购买足够的食物
if i == n - 1 and current_food > 0:#判断最后一天是否需要购买
break
total_cost += buy_amount * data[i]
current_food = buy_amount +current_food
else:
# 如果不是,只购买一天的食物
total_cost += data[i]
current_food = 1
# 每天消耗一份食物
current_food -= 1
return total_cost
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(13, 6, [6,19,19,3,3,25,16,17,8,1,5,21,2]))#应该输出40才对
调试了之后发现主要问题是,无法计算如何购买足够撑到最便宜那天的食物数量,不知道也没有大神可以给给建议
滑动窗口+双端队列(正确代码)
from collections import deque
def solution(n: int, k: int, data: list) -> int:
"""这两个断言用于确保输入的 n 和 k 满足题目要求。"""
assert n == len(data)
assert k < n
mins = deque()#双端队列 deque 来维护一个单调递增的队列,用于存储价格信息。
result = 0
for j in range(n):
# 如果当前队列不为空,且队列中的最后一个元素的值大于当前元素,则移除队列中的元素
while len(mins) > 0 and mins[-1][1] > data[j]:
mins.pop()
mins.append([j, data[j]])# 将当前元素的索引和数据添加到队列
while mins[0][0] <= j - k:# 如果队列头部的元素不在滑动窗口范围内,移除队列头部的元素
mins.popleft()
# 累加当前窗口的最小值(队列头部的值)
result += mins[0][1]
return result
if __name__ == "__main__":
# Add your test cases here
print(solution(5, 2, [1, 2, 3, 3, 2]) == 9)
这个算法的时间复杂度是 O(n),因为每个元素最多被加入和移除队列一次
对于每个索引 j,我们维护一个窗口,该窗口的大小最大为 k,并确保队列中存储的是当前窗口的最小值。
我们通过双端队列(deque)来维护窗口中的最小值。双端队列的优势在于,它允许我们以常数时间复杂度 O(1) 从队列的两端删除元素,从而加快了计算过程。
示例解释:
假设我们调用 solution(5, 2, [1, 2, 3, 3, 2]):
初始时,mins 是空的,result 为 0。
对于每个元素,维护队列,使得队列始终存储当前窗口的最小值。
j = 0:当前元素为 1,队列为空,加入队列 [0, 1]。窗口最小值为 1,result 变为 1。
j = 1:当前元素为 2,队列中的元素 1 小于 2,加入队列 [0, 1], [1, 2]。窗口最小值为 1,result 变为 2。
j = 2:当前元素为 3,队列中的元素 1 小于 3,加入队列 [0, 1], [1, 2], [2, 3]。窗口最小值为 1,result 变为 3。
j = 3:当前元素为 3,队列中的元素 1 小于 3,加入队列 [0, 1], [1, 2], [2, 3], [3, 3]。窗口最小值为 2,result 变为 5。
j = 4:当前元素为 2,队列中的元素 2 和 3 被移除,加入队列 [1, 2], [4, 2]。窗口最小值为 2,result 变为 7。
最终返回的结果为 9。