题目解析:徒步旅行中的补给问题
一.问题描述
给定一个总路程N天,最大携带食物K份,以及一个长度为N的数组data,这个数组装载着每一天购买食物的单价,假设现在,你是那个走线的人,每天你都需要消耗一份食物,而这些食物只有在路上才能购买,问你的最小花费应该是多少?
二.思考如何解决
像是这样的问题,一般会采用动态规划以及贪心来解决,具体选择哪种方法,还是要看每个人的思考,我个人是更喜欢贪心算法,因为我觉得贪心算法更符合人的直觉思维流程,而且比较简单。
1.动态规划
动态规划,使用dp数组解决这道题, 首先也需要搞懂这道题的逻辑是什么。 再来定义我们的状态数组。
无非就是买和不买的问题,买的话,买几份,不买的话,会怎么样。 那依据我们的思考,就可以定义状态数组了。
我想到的状态数组:
其中i代表第几天, j代表还有多少份食物,
那么dp[i][j]就代表第i天留下j份食物的最小花费。
对于每一天, 有两种选择,第一种,不买食物,第二种,买k份食物
状态转移方程可以如下:
对于不购买食物的一天,dp数组应该为: dp[i][j] = dp[i-1][j+1],
由于需要消耗一份食物,所以剩下的食物是前一天的食物基础上减去1。
对于购买k份食物的一天,dp数组应该为: dp[i][j] = dp[i-1][j-k+1] + k * cost[i-1],
同样是要消耗一份食物,但因为会购买K份食物, 所以比前一天多加了k-1份食物。 具体实现代码如下:
def solution(N, K, cost):
# 初始化dp数组,大小为(N+1) x (K+1),所有元素设为无穷大
dp = [[float('inf')] * (K + 1) for _ in range(N + 1)]
dp[0][0] = 0
# 初始条件
# 遍历每一天, 从第一天开始, 一直到第N天
for i in range(1, N + 1):
# 遍历可能的食物数量, 从背包里没有食物, 到背包有K份食物
for j in range(K + 1):
# 不购买食物的情况下, 每一天都会比上一天拥有的食物减少一
if j + 1 <= K:
dp[i][j] = dp[i-1][j+1]
# 购买k份食物情况下, 花费就要提高k份食物的钱, 同时食物提高K-1份
for k in range(1, K+1):
if j + 1 <= K:
dp[i][j] = min(dp[i][j], dp[i-1][j-k+1] + k * cost[i-1])
# 返回结果, 最终手里的食物肯定是刚刚好吃完的, 也就是第N天手里还剩下0份食物。
return dp[N][0]
这是采用动态规划,它的特点是所有情况都必须考虑一遍,可能会导致一些资源的浪费,事实上,如果考虑性能的话,使用贪心算法可能会更好一些。
2.贪心算法
贪心算法,会更符合我们人脑的思维,在这道题中,同样也可以采用贪心算法解决。 首先,在问题的这种情况下,人会怎么做呢?本人是这样想的:对于每一天,无非也就是买不买的问题
什么时候应该购买食物? 买多少份?
(1) 现阶段没有食物的时候,不管今天补给站的食物有多贵,最少必须买一份。也可能买多份,这要看第二天以及第三天的食物是不是比当天贵。如果是,直接在当天买就可以避免在更贵的天数买了。
(2) 由于我们的背包大小只有K,所以我们最多把食物买到K份,所以每天我们只需把目光放到未来的K-1天就可以了, 因为超过了K天的事情,今天的买与不买是不影响的。
根据这两条原理,代码的逻辑如下:
- 对于每一天(从第 0 天到第 n-1 天),小R会查看接下来的 k 天(如果剩余天数少于 k 天,则查看剩余的所有天数)的补给站价格,找出其中的最低价格。
- 如果今天是未来K天最低价格,小R直接把背包装满,或者买够到最后一天的食物。
- 如果最低价格不在当前天,小R会寻找比今天价格更低的食物点,比如今天是4块, 明天是5块,后天是3块, 我就知道后天的价格比今天要便宜, 那我就把今天的食物补充到下一个更加便宜的地点。
- 如果剩余的天数小于等于当前食物数量,小R可以停止购买食物,因为已经足够覆盖剩余的天数。
实现代码如下:
def solution(n, k, data):
total_cost = 0
current_food = 0
# 贪心算法实现逻辑, 遍历接下来的每一天
for day in range(n):
# 查看未来K天的最低价格
search_date = data[day:day+k]
min_cost_date = min(search_date)
min_search_place = search_date.index(min_cost_date)
# 今天是未来K天的最低价格
if min_search_place == 0:
# 买到背包爆满
if n-day > k:
total_cost += (k-current_food) * data[day]
current_food += (k - current_food)
# 或者买够到旅途结束
else:
total_cost += ((n-day)-current_food) * data[day]
current_food += ((n-day) - current_food)
# 今天不是未来K天的最低价格
else:
if data[day] != min(search_date[:min_search_place]):
# 找到未来K天内,比今天更便宜的那一天,是哪一天
for i in search_date:
if data[day] > i:
next_small_position = search_date.index(i)
break
# 如果当前食物不足以支持到那一天, 买够支撑到那天的食物
if current_food == 0 or current_food < next_small_position:
total_cost += data[day] * (next_small_position - current_food)
current_food += (next_small_position - current_food)
else:
# 如果当前食物不够支持到未来k天内最低价格, 买够支撑到那天的食物
if current_food < min_search_place:
total_cost += (min_search_place - current_food) * data[day]
current_food += (min_search_place - current_food)
# 每天的消耗
current_food -= 1
if(n - (day+1) <= current_food):
break
return total_cost
总体的代码实现就是这样, 也许会有些可以优化的地方。
心得总结:
动态规划和贪心算法,都是非常好的算法,一个能够知道所有的情况,一个可以用最快的速度得出想要的结果,像这种问题,既能采用dp,也可以采用贪心,其实我是更加喜欢贪心一点,我觉得它更符合我脑袋里的思维,dp的话呢,总觉得有点哪里不对。不过都是非常好的算法了。