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

214 阅读6分钟

题目解析:徒步旅行中的补给问题

一.问题描述

给定一个总路程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的话呢,总觉得有点哪里不对。不过都是非常好的算法了。