我在网上发现了这个问题:
你有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();
}
}
答案 0 :(得分:3)
是的,如果您以天真的方式执行此操作,您的递归树将变大。假设你有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.首先使第一个数字尽可能大,这将是{ {1}}。在我的例子中,那将是:
N-(M*(K-1))
给出答案[6,2,2]。
然后,您可以通过从左到右“移动”值来构建算法来调整数字以得出有效组合。在我的例子中,你有:
10 - 2*(3-1) = 6
通过确保值从左向右递减,可以避免看似无限的递归。例如,在上面你永远不会有[3,4,3]。
如果您确实需要所有有效排列,则可以为上述每种组合生成排列。但我怀疑这不是必要的。
我认为这应该足以让你开始寻求一个好的解决方案。
答案 1 :(得分:2)
一种解决方案是从k-1个房间的结果中计算k个房间的结果。
我已经简化了一个允许在一个房间存储0吨的问题。如果我们必须存储至少1个,我们可以提前减去它并将房间容量减少1个。
所以我们定义一个函数calc:(Int,Int)=&gt; List [Int]计算多个房间,容量计算组合数量列表。第一个条目包含我们用于存储0的组合数,存储1时的下一个条目,依此类推。
我们可以轻松地为一个房间计算此功能。因此calc(1,m)给出了一个直到第m个元素的列表,然后它只包含零。
对于更大的k,我们可以递归地定义这个函数。我们只计算calc(k - 1,m),然后通过总结旧列表的前缀来构建新列表。例如。如果我们要存储5吨,我们可以在第一个房间存储所有5个,在以下房间存储0个,或者在第一个房间存储4个,在下面的1个存储,依此类推。所以我们必须总结其余房间的0到5的组合。
由于我们有最大容量,我们可能不得不省略一些组合,即如果房间只有容量3,我们不能计算在其余房间存储0和1吨的组合。
我在Scala中实现了这种方法。我已经使用了流(即无限列表)但是你知道你需要的最大元素数量,这是没有必要的。
方法的时间复杂度应为O(k * n ^ 2)
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)
}
你可以在这里试试:
(我已经将BigInt替换为Long,以使其适用于更大的数字)
答案 2 :(得分:1)
首先提示,删除distributionsRecursive
,不要建立解决方案列表。所有解决方案的列表都是一个庞大的数据集。只需计算一下。
这将允许您将possibleDistributions
转换为根据自身定义的递归函数。递归步骤为possibleDistributions(food, rooms, roomCapacity) = sum from i = 1 to roomCapacity of possibleDistributions(food - i, rooms - 1, roomCapacity)
。
您将节省大量内存,但仍存在潜在的性能问题。但是,使用纯递归函数,您现在可以使用https://en.wikipedia.org/wiki/Memoization修复此问题。