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

159 阅读4分钟

题目描述

小R计划进行一次从地点A到地点B的徒步旅行,这次旅行总共需要 NN 天。为了保持充足的能量,小R每天必须消耗1份食物。幸运的是,小R每天都会经过一个补给站,可以在此购买食物进行补充。然而,每个补给站的食物价格各不相同,每份食物的价格由数组 datadata 给出,其中 data[i]data[i] 表示第 ii 天补给站的食物单价。此外,小R最多只能同时携带 KK 份食物。

小R希望在保证每天都有食物的前提下,以最小的花费完成这次徒步旅行。你的任务是帮助小R计算出最低的总花费。

解题思路

解题思路

这是一道典型的动态规划问题,需要考虑在每一天购买多少食物才能以最小的成本完成整个旅程。关键在于决策小R在每个补给站购买的食物数量,使得在满足每天消耗1份食物和背包容量不超过 KK 的限制下,总花费最小。

我们可以使用动态规划来解决这个问题,定义状态和状态转移方程如下:

  • 状态定义:设 dp[i][j]dp[i][j] 表示在第 i 天购物前,背包中有 j 份食物,完成剩余旅程所需的最小总花费。

  • 初始状态

    • 对于最后一天(第 N1N-1 天),我们需要确保小R有足够的食物完成当天的消耗。

      • 如果此时背包中有0份食物( j=0j=0 ),则必须购买1份食物,花费为 data[N1]data[N−1]
      • 如果背包中有食物( j>0j>0 ),则不需要购买,花费为0。
  • 状态转移方程

    • 对于第 ii 天,考虑当前背包中有 jj 份食物,可能的决策是购买 tt 份食物( 0tKj0≤t≤K−j ),使得背包中的食物数量不超过 KK
    • 每次购买后,背包中的食物数量变为 j+t1j+t−1(消耗1份食物),下一状态为 dp[i+1][j+t1]dp[i+1][j+t−1]
    • 我们需要在所有可能的 tt 中选择使得总花费 cost=t×data[i]+dp[i+1][j+t1]cost=t×data[i]+dp[i+1][j+t−1] 最小的方案。
  • 目标:计算 dp[0][0]dp[0][0],即从第0天开始,背包中没有食物的情况下完成旅程的最小总花费。

代码实现

public class Main {
    public static int solution(int n, int k, int[] data) {
        // dp[i][j]表示:在第i天结束时,背包中有j份食物,完成剩余旅程的最小花费
        int[][] dp = new int[n][k + 1];
        
        // 初始化最后一天的dp数组
        for (int foodCnt = 0; foodCnt <= k; foodCnt++) {
            if (foodCnt == 0) {
                // 背包为空,需要购买1份食物
                dp[n - 1][foodCnt] = data[n - 1];
            } else {
                // 背包有食物,不需要购买
                dp[n - 1][foodCnt] = 0;
            }
        }

        // 从倒数第二天开始递推
        for (int nthDay = n - 2; nthDay >= 0; nthDay--) {
            for (int curFoodCnt = 0; curFoodCnt <= k; curFoodCnt++) {
                int minCost = Integer.MAX_VALUE;
                // 枚举在当前补给站购买的食物数量
                for (int addFoodCnt = 0; addFoodCnt + curFoodCnt <= k; addFoodCnt++) {
                    // 购买的食物花费
                    int needMoney = addFoodCnt * data[nthDay];
                    // 计算下一状态的食物数量
                    int nextFoodCnt = addFoodCnt + curFoodCnt - 1;
                    if (nextFoodCnt >= 0) {
                        // 更新最小花费
                        minCost = Math.min(minCost, needMoney + dp[nthDay + 1][nextFoodCnt]);
                    }
                }
                dp[nthDay][curFoodCnt] = minCost;
            }
        }

        // 返回从第0天开始,背包为空的最小总花费
        return dp[0][0];
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(5, 2, new int[]{1, 2, 3, 3, 2}) == 9);
        System.out.println(solution(6, 3, new int[]{4, 1, 5, 2, 1, 3}) == 9);
        System.out.println(solution(4, 1, new int[]{3, 2, 4, 1}) == 10);
    }
}

代码详解

  • dp数组的定义:使用一个二维数组 dp[n][k+1]dp[n][k+1] 来存储状态,其中 nn 是天数,k+1k+1是可能的食物数量(从0到KK)。

  • 初始化

    • 对于最后一天,如果背包中没有食物( foodCnt=0foodCnt=0 ),必须购买1份食物,花费为 data[n1]data[n−1]
    • 如果背包中有食物( foodCnt>0foodCnt>0),则不需要购买,花费为0。
  • 动态规划递推

    • 外层循环从第 n2n−2 天开始递推到第0天。
    • 内层循环遍历当前可能的食物数量 curFoodCntcurFoodCnt
    • 对于每个可能的购买数量 addFoodCntaddFoodCnt,计算购买花费 needMoneyneedMoney 和下一状态的食物数量 nextFoodCntnextFoodCnt
    • 更新 dp[nthDay][curFoodCnt]dp[nthDay][curFoodCnt] 为最小的总花费。
  • 返回结果:最后返回 dp[0][0]dp[0][0],即从第0天开始,背包为空的情况下的最小总花费。

时间复杂度和空间复杂度分析

  • 时间复杂度

    • 外层循环遍历 nn 天,时间复杂度为 O(n)O(n)
    • 内层循环遍历背包可能的食物数量,复杂度为 O(k)O(k)
    • 最内层循环遍历可能的购买数量,复杂度为 O(k)O(k)
    • 总的时间复杂度为 O(n×k2)O(n×k^2)
  • 空间复杂度

    • 使用了一个二维数组 dp[n][k+1]dp[n][k+1],空间复杂度为 O(n×k)O(n×k)

总结

这道题目考察了动态规划的应用,特别是多维状态下的最优化问题。通过精确地定义状态和状态转移方程,我们能够有效地解决问题。关键在于理解在每一天可能的决策,以及如何在满足约束条件下找到最小的总花费。时间和空间复杂度虽然较高,但在 nnkk 不大的情况下,仍然是可行的。