总计为A的N个整数的不同组的数量

时间:2012-10-12 10:45:10

标签: java math combinatorics

我想计算N个整数的不同有序组的数量,以便每个组的元素总和为A

例如:如果N = 3且A = 3,则结果应为10:
1 = [3,0,0]
2 = [2,1,0]
3 = [1,2,0] 4 = [0,3,0]
5 = [2,0,1]
6 = [1,1,1]
7 = [0,2,1]
8 = [1,0,2]
9 = [0,1,2]
10 = [0,0,3]

我这样做的方式是蛮力:

public static int calc(int a, int n){
    if (n <= 1 || a == 0) return 1;

    int sum = 0;
    for (int i=0; i<=n; i++)
        sum += calc(a - i, n - 1);

    return sum;
}

我怀疑可能有更好的方法(我错过了一些数学计算......) 在那里?

修改 在原始问题中,我忘了考虑订单

5 个答案:

答案 0 :(得分:5)

这是A到N部分(包括零部分)的组合成分。对(A,N)的组成数等于C(A + N-1,A),其中C()是组合数,即二项式系数。请参阅相同的公式herehere

答案 1 :(得分:3)

想象一下长度A的大段。并设想N-1有序分隔符,将分段划分为多个部分。因此,每个部分都是一个加数,而整个部分是一个总和。

Ergo,您只需要提供枚举分隔符位置的算法。

您可以放入任何N+1个位置的第一个分隔符P_0 = {0,1,... N}

第二个分隔符可以进入P_1 = {P_0,... N}

中的任何一个

等等。

您可以使用递归来实现此目的。

答案 2 :(得分:2)

我确信有一个数学计算来回答这个问题,但由于这是一个编程Q&amp; A,我会告诉你如何让你的程序更快地给出答案:你可以使用memoization。< / p>

目前,您的计划每次都会重新计算calc(a, n)的答案。但是,答案可以计算一次,因为它在后续调用中不会更改。为calc(a,n)的结果添加一个2D数组,用-1初始化,并在计算结果之前使用它来查找结果,以节省大量时间反复重复计算相同的数字:

private static int[][] memo = new int[30][30];
static {
    for(int i = 0 ; i != 30 ; i++)
        for(int j = 0 ; j != 30 ; j++)
            memo[i][j] = -1;
}
public static int calc(int a, int n){
    if (n <= 1 || a == 0) return 1;
    if (memo[a][n] > 0) return memo[a][n];
    int sum = 0;
    for (int i=0; i<=n; i++)
        sum += calc(a - i, n - 1);
    return (memo[a][n] = sum);
}

答案 3 :(得分:1)

对于枚举:使用上面其他解决方案中给出的公式,效率更高。除非需要,否则您永远不想实际生成一整套n整数合成。它们具有难以处理的属性,特别是如果您只想要总计它们,而不是生成它们。生成它们是另一个问题......

用于生成:使用无环路算法...有很多O(1) - 灰色代码序列结果。限制整数组合的变化非常少,没有或没有无循环算法。在整类组合的这类问题中有许多算法,其中大多数是非常具体的,但是对于这个特定问题存在大量现代无循环算法。超级高效。除非你有大量的并行计算功能,否则蛮力绝不是解决这个问题的方法。 Google或Google Scholar随时为您服务! :d

希望这有帮助!

答案 4 :(得分:0)

我找到了另一个解决方案,只是递归而没有分隔符:

public class App201210121604 {

public static Vector<int[]> split(int sum, int count) {

    if( sum < 0 ) {
        throw new IllegalArgumentException("Negative sum is not allowed");
    }

    Vector<int[]> ans = new Vector<int[]>();

    // "reserved" end of recursion
    if( count <= 0 ) {
        // nothing to do
    }

    // end of recursion
    else if( count == 1 ) {
        ans.add(new int[] {sum});
    }

    // body of recursion
    else {
        // for each first summand from 0 to summ
        for(int i=0; i<=sum; ++i) {

            // do a recursion to get the "tail"
            for(int[] tail : split(sum-i, count-1)) {

                int[] group = new int[count];
                group[0] = i;
                System.arraycopy(tail, 0, group, 1, count-1);

                ans.add(group);
            }
        }
    }

    return ans;
}

public static void main(String[] args) {

    Vector<int[]> ans = split(8, 4);

    for(int[] group : ans) {
        for(int i=0; i<group.length; ++i) {
            if( i>0 ) System.out.print("+");
            System.out.print(group[i]);
        }
        System.out.println("");
    }
}

}