水果店果篮最小成本问题 | 豆包MarsCode AI刷题

126 阅读5分钟

这个题其实蛮有意思的,比较贴合日常生活场景,我用了动态规划dp的思想结合队列(Queue)和动态规划表,我问了al,这题还可以结合其他数据结构类型来写,有机会你们可以试试!

问题描述

小C开了一家水果店,某天接到了一个大订单,要求将n个编号为1到n的水果打包成多个果篮,每个果篮的最大容量为m个水果,并且果篮中水果的编号必须连续。每个果篮的成本与容纳的水果数量及水果的体积相关,成本公式为:

                    k×⌊(u+v)/2⌋+sk×⌊(u+v)/2⌋+s

其中,uu是果篮中水果的最大体积,vv是果篮中水果的最小体积,kk是果篮中水果的数量,ss是一个常数,⌊x⌋⌊x⌋ 表示对xx进行下取整。

你的任务是帮助小C将这些水果打包成若干果篮,使得总成本最小。

例如:当有6个水果,体积为[1, 4, 5, 1, 4, 1],一个果篮最多装4个水果,常数ss为3时,最优的打包方案是将前三个水果(1, 4, 5)装成一个果篮,后三个水果(1, 4, 1)装成另一个果篮,最小的总成本为21。


测试样例

样例1:

输入:n = 6, m = 4, s = 3, a = [1, 4, 5, 1, 4, 1]
输出:21

样例2:

输入:n = 5, m = 3, s = 2, a = [2, 3, 1, 4, 6]
输出:17

样例3:

输入:n = 7, m = 4, s = 5, a = [3, 6, 2, 7, 1, 4, 5]
输出:35

题目分析

输入


  • n:水果的总数量;
  • m:每个果篮的最大容量;
  • s:常数 s;
  • a:一个数组,表示每个水果的体积;

输出分析

一个整数,表示将所有水果打包成若干果篮的最小总成本。

-样例1:

  • 输入:n = 6, m = 4, s = 3, a = [1, 4, 5, 1, 4, 1]
  • 输出:21
  • 解释:
  • 最优的打包方案是将前三个水果(1, 4, 5)装成一个果篮,后三个水果(1, 4, 1)装成另一个果篮
  • 第一个果篮的成本:3×(5+1)/2+3=3×3+3=123 \times \lfloor(5+1)/2\rfloor + 3 = 3 \times 3 + 3 = 12
  • 第二个果篮的成本:3×(4+1)/2+3=3×2+3=93 \times \lfloor(4+1)/2\rfloor + 3 = 3 \times 2 + 3 = 9
  • 总成本:12+9=2112 + 9 = 21

- 样例2:

  • 输入:n = 5, m = 3, s = 2, a = [2, 3, 1, 4, 6]
  • 输出:17

解释:

  • 最优的打包方案是将前三个水果(2, 3, 1)装成一个果篮,后两个水果(4, 6)装成另一个果篮
  • 第一个果篮的成本:3×(3+1)/2+2=3×2+2=83 \times \lfloor(3+1)/2\rfloor + 2 = 3 \times 2 + 2 = 8
  • 第二个果篮的成本:2×(6+4)/2+2=2×5+2=122 \times \lfloor(6+4)/2\rfloor + 2 = 2 \times 5 + 2 = 12
  • 总成本:8+12=208 + 12 = 20

样例3:

  • 输入:n = 7, m = 4, s = 5, a = [3, 6, 2, 7, 1, 4, 5]
  • 输出:35
解释:
  • 最优的打包方案是将前四个水果(3, 6, 2, 7)装成一个果篮,后三个水果(1, 4, 5)装成另一个果篮
  • 第一个果篮的成本:4×(7+2)/2+5=4×4+5=214 \times \lfloor(7+2)/2\rfloor + 5 = 4 \times 4 + 5 = 21
  • 第二个果篮的成本:3×(5+1)/2+5=3×3+5=143 \times \lfloor(5+1)/2\rfloor + 5 = 3 \times 3 + 5 = 14
  • 总成本:21+14=3521 + 14 = 35

代码实战方法一:使用数组和动态规划表

数据结构选择

  • 你可以使用数组来存储水果的体积。
  • 需要一个方法来计算每个果篮的成本

算法步骤

  • 遍历所有可能的果篮组合,计算每个组合的成本。
  • 使用动态规划(Dynamic Programming)来记录每个子问题的最优解,从而避免重复计算。
  • 最终得到总成本最小的打包方案。

代码分享

import java.util.Arrays;

public class Main {
    public static int solution(int n, int m, int s, int[] a) {
        // 初始化动态规划数组
        int[] dp = new int[n + 1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;

        // 遍历每个水果
        for (int i = 1; i <= n; i++) {
            int maxVolume = a[i - 1];
            int minVolume = a[i - 1];
            int count = 0;

            // 尝试将当前水果与之前的水果打包成一个果篮
            for (int j = i; j > 0 && count < m; j--) {
                maxVolume = Math.max(maxVolume, a[j - 1]);
                minVolume = Math.min(minVolume, a[j - 1]);
                count++;

                // 计算当前果篮的成本
                int cost = count * ((maxVolume + minVolume) / 2) + s;

                // 更新动态规划数组
                dp[i] = Math.min(dp[i], dp[j - 1] + cost);
            }
        }

        return dp[n];
    }

    public static void main(String[] args) {
        System.out.println(solution(6, 4, 3, new int[]{1, 4, 5, 1, 4, 1}) == 21);
        System.out.println(solution(5, 3, 2, new int[]{2, 3, 1, 4, 6}) == 17);
        System.out.println(solution(7, 4, 5, new int[]{3, 6, 2, 7, 1, 4, 5}) == 35);
    }
}

时间复杂就不写了,偷个懒;


代码实战方法二:使用优先队列结构(多尝试才会有收获)

使用优先队列的思路

  1. 状态定义

    • dp[i] 表示前 i 个水果的最小成本。
  2. 状态转移

    • 对于每个 i,我们需要考虑将第 i 个水果放入一个新果篮,或者将其加入到前一个果篮中。
    • 使用优先队列来维护当前果篮中的最大和最小体积。
  3. 初始条件

    • dp[0] = 0,表示没有水果时的成本为0。
  4. 目标

    • 求 dp[n],即前 n 个水果的最小成本。

具体实现

  1. 使用两个优先队列

    • 一个用于维护当前果篮中的最大体积。
    • 另一个用于维护当前果篮中的最小体积。
  2. 动态规划的实现

    • 使用一个嵌套循环来计算每个 dp[i] 的值。
    • 在每个果篮中添加新的水果时,更新优先队列中的最大和最小体积。

代码分享

import java.util.Arrays;
import java.util.PriorityQueue;

public class Main {
   public static int solution(int n, int m, int s, int[] a) {
       // 初始化动态规划数组
       int[] dp = new int[n + 1];
       Arrays.fill(dp, Integer.MAX_VALUE);
       dp[0] = 0;

       // 优先队列,一个用于维护最大体积,一个用于维护最小体积
       PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a1, b1) -> b1 - a1);
       PriorityQueue<Integer> minHeap = new PriorityQueue<>();

       // 遍历每个水果
       for (int i = 1; i <= n; i++) {
           // 清空优先队列
           maxHeap.clear();
           minHeap.clear();

           // 尝试将当前水果与之前的水果打包成一个果篮
           for (int j = i; j > 0 && (i - j + 1) <= m; j--) {
               maxHeap.add(a[j - 1]);
               minHeap.add(a[j - 1]);

               int maxVolume = maxHeap.peek();
               int minVolume = minHeap.peek();
               int count = i - j + 1;

               // 计算当前果篮的成本
               int cost = count * ((maxVolume + minVolume) / 2) + s;

               // 更新动态规划数组
               dp[i] = Math.min(dp[i], dp[j - 1] + cost);
           }
       }

       return dp[n];
   }

   public static void main(String[] args) {
       System.out.println(solution(6, 4, 3, new int[]{1, 4, 5, 1, 4, 1}) == 21);  // 输出: true
       System.out.println(solution(5, 3, 2, new int[]{2, 3, 1, 4, 6}) == 17);     // 输出: true
       System.out.println(solution(7, 4, 5, new int[]{3, 6, 2, 7, 1, 4, 5}) == 35); // 输出: true
   }
}

总结

文章有点长,应该很少人看到这里吧!对于看到这里的同道中人,嗯,这题很适合练手,还可以用其他数据结构写,比如哈希表,二维数组(2D Array)...........,感兴趣的话,你们可以试着写写!