Java 在容量为M的K个房间中分配N吨食物

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

我在网上发现了这个问题:

你有N吨食物和K个房间来储存它们。每个房间的容量为M。您可以通过多少种方式在房间中分配食物,以便每个房间至少有1吨食物

我的方法是递归地找到满足问题条件的所有可能的变化。我从一个大小为K的数组开始,初始化为1。然后我不断地向数组的每个元素添加1,并递归地检查新数组是否满足条件。然而,递归树变得太大太快,并且对于稍高的N、K和M值,程序花费的时间太长

实现这项任务的更有效算法是什么?对现有的算法实现是否有任何优化

这是我的实现:

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)
  }