Java 在容量为M的K个房间中分配N吨食物
我在网上发现了这个问题: 你有N吨食物和K个房间来储存它们。每个房间的容量为M。您可以通过多少种方式在房间中分配食物,以便每个房间至少有1吨食物 我的方法是递归地找到满足问题条件的所有可能的变化。我从一个大小为K的数组开始,初始化为1。然后我不断地向数组的每个元素添加1,并递归地检查新数组是否满足条件。然而,递归树变得太大太快,并且对于稍高的N、K和M值,程序花费的时间太长 实现这项任务的更有效算法是什么?对现有的算法实现是否有任何优化 这是我的实现:Java 在容量为M的K个房间中分配N吨食物,java,algorithm,performance,recursion,Java,Algorithm,Performance,Recursion,我在网上发现了这个问题: 你有N吨食物和K个房间来储存它们。每个房间的容量为M。您可以通过多少种方式在房间中分配食物,以便每个房间至少有1吨食物 我的方法是递归地找到满足问题条件的所有可能的变化。我从一个大小为K的数组开始,初始化为1。然后我不断地向数组的每个元素添加1,并递归地检查新数组是否满足条件。然而,递归树变得太大太快,并且对于稍高的N、K和M值,程序花费的时间太长 实现这项任务的更有效算法是什么?对现有的算法实现是否有任何优化 这是我的实现: import java.util.Arra
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
public class Main {
// keeping track of valid variations, disregarding duplicates
public static HashSet<String> solutions = new HashSet<>();
// calculating sum of each variation
public static int sum(int[] array) {
int sum = 0;
for (int i : array) {
sum += i;
}
return sum;
}
public static void distributionsRecursive(int food, int rooms, int roomCapacity, int[] variation, int sum) {
// if all food has been allocated
if (sum == food) {
// add solution to solutions
solutions.add(Arrays.toString(variation));
return;
}
// keep adding 1 to every index in current variation
for (int i = 0; i < rooms; i++) {
// create new array for every recursive call
int[] tempVariation = Arrays.copyOf(variation, variation.length);
// if element is equal to room capacity, can't add any more in it
if (tempVariation[i] == roomCapacity) {
continue;
} else {
tempVariation[i]++;
sum = sum(tempVariation);
// recursively call function on new variation
distributionsRecursive(food, rooms, roomCapacity, tempVariation, sum);
}
}
return;
}
public static int possibleDistributions(int food, int rooms, int roomCapacity) {
int[] variation = new int[rooms];
// start from all 1, keep going till all food is allocated
Arrays.fill(variation, 1);
distributionsRecursive(food, rooms, roomCapacity, variation, rooms);
return solutions.size();
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int food = in.nextInt();
int rooms = in.nextInt();
int roomCapacity = in.nextInt();
int total = possibleDistributions(food, rooms, roomCapacity);
System.out.println(total);
in.close();
}
}
导入java.util.array;
导入java.util.HashSet;
导入java.util.Scanner;
公共班机{
//跟踪有效的变更,忽略重复项
public static HashSet solutions=new HashSet();
//计算每个变化的总和
公共静态整数和(整数[]数组){
整数和=0;
for(int i:array){
总和+=i;
}
回报金额;
}
公共静态空隙分布递归(整数食物、整数房间、整数房间容量、整数[]变化、整数总和){
//如果所有食物都分配好了
如果(总和==食物){
//将解决方案添加到解决方案中
add(Arrays.toString(variation));
返回;
}
//在当前变量中的每个索引中不断添加1
对于(int i=0;i
是的,如果您以一种幼稚的方式执行此操作,您的递归树将变大。假设你有10吨,3个房间,M=2。一种有效的安排是[2,3,5]。但是你也有[2,5,3],[3,2,5],[3,5,2],[5,2,3]和[5,3,2]。所以对于每个有效的数字分组,实际上都有K!排列
解决这个问题的一个可能更好的方法是确定有多少种方法可以使K个数字(最小M和最大N)相加为N。首先让第一个数字尽可能大,即N-(M*(K-1))
。在我的例子中,这将是:
10 - 2*(3-1) = 6
给出答案[6,2,2]
然后,您可以构建一个算法,通过从左向右“移动”值来调整数字,从而得出有效的组合。在我的示例中,您将有:
6,2,2
5,3,2
4,4,2
4,3,3
通过确保值从左向右递减,可以避免看似无限的递归。例如,在上面的例子中,你永远不会有[3,4,3]
如果您确实想要所有有效的排列,您可以为上面的每个组合生成排列。不过,我想这是没有必要的
我认为这应该足以让你开始找到一个好的解决方案。第一个技巧,删除
分发递归
,不要建立解决方案列表。所有解决方案的列表都是一个庞大的数据集。只要数一数
这将允许您将可能的分布转换为一个根据自身定义的递归函数。递归步骤是,可能分布(食物,房间,房间容量)=从i=1到可能分布(食物-i,房间-1,房间容量)的房间容量之和
您将节省大量内存,但仍存在潜在的性能问题。但是,使用纯递归函数,您现在可以通过解决此问题。一个解决方案是从k-1房间的结果计算k房间的结果
我简化了一点问题,允许在一个房间里储存0吨。如果我们必须存储至少1个,我们可以提前减去这个,将房间的容量减少1
所以我们定义了一个函数calc:(Int,Int)=>List[Int],它为一系列房间和一个容量计算一系列组合。第一个条目包含存储0时得到的组合数,下一个条目包含存储1时得到的组合数,依此类推
我们可以很容易地计算一个房间的这个函数。所以calc(1,m)给了我们一个到第m个元素的1的列表,然后它只包含0
对于较大的k,我们可以递归地定义这个函数。我们只需计算calc(k-1,m),然后通过对旧列表的前缀求和来构建新列表。例如,如果我们想储存5吨,我们可以在第一个房间储存全部5吨,在后面的房间储存0吨,或者在第一个房间储存4吨,在后面的房间储存1吨,依此类推。所以我们必须对其余房间的0到5的组合求和
由于我们有一个最大容量,我们可能不得不省略一些组合,即,如果房间
def calc(rooms: Int, capacity: Int): Stream[Long] =
if(rooms == 1) {
Stream.from(0).map(x => if(x <= capacity) 1L else 0L)
} else {
val rest = calc(rooms - 1, capacity)
Stream.from(0).map(x => rest.take(x+1).drop(Math.max(0,x - capacity)).sum)
}