这个题其实蛮有意思的,比较贴合日常生活场景,我用了动态规划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)装成另一个果篮。
- 第一个果篮的成本:
- 第二个果篮的成本:
- 总成本:
- 样例2:
- 输入:n = 5, m = 3, s = 2, a = [2, 3, 1, 4, 6]
- 输出:17
解释:
- 最优的打包方案是将前三个水果(2, 3, 1)装成一个果篮,后两个水果(4, 6)装成另一个果篮。
- 第一个果篮的成本:
- 第二个果篮的成本:
- 总成本:
样例3:
- 输入:n = 7, m = 4, s = 5, a = [3, 6, 2, 7, 1, 4, 5]
- 输出:35
解释:
- 最优的打包方案是将前四个水果(3, 6, 2, 7)装成一个果篮,后三个水果(1, 4, 5)装成另一个果篮。
- 第一个果篮的成本:
- 第二个果篮的成本:
- 总成本:
代码实战方法一:使用数组和动态规划表
数据结构选择
- 你可以使用数组来存储水果的体积。
- 需要一个方法来计算每个果篮的成本
算法步骤
- 遍历所有可能的果篮组合,计算每个组合的成本。
- 使用动态规划(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);
}
}
时间复杂就不写了,偷个懒;
代码实战方法二:使用优先队列结构(多尝试才会有收获)
使用优先队列的思路
-
状态定义:
dp[i]表示前i个水果的最小成本。
-
状态转移:
- 对于每个
i,我们需要考虑将第i个水果放入一个新果篮,或者将其加入到前一个果篮中。 - 使用优先队列来维护当前果篮中的最大和最小体积。
- 对于每个
-
初始条件:
dp[0] = 0,表示没有水果时的成本为0。
-
目标:
- 求
dp[n],即前n个水果的最小成本。
- 求
具体实现
-
使用两个优先队列:
- 一个用于维护当前果篮中的最大体积。
- 另一个用于维护当前果篮中的最小体积。
-
动态规划的实现:
- 使用一个嵌套循环来计算每个
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)...........,感兴趣的话,你们可以试着写写!