查找大n的组合数C(n,r)(十进制表示精度)

时间:2012-11-05 22:05:43

标签: java algorithm bigdecimal

这是CodeSprint3的问题 https://cs3.interviewstreet.com/challenges/dashboard/#problem/50877a587c389 基本上问题是计算给定n和r的可能组合的数量nCr。此外,1 <= n <= 1000000000且0 <= r <= n。 以模数142857输出所有答案。

 Since 6C4=6!/4! 2!
        =6*5/2!     
        =6*5/2*1

我认为在每一步使用分割都可以避免溢出。也就是说 以n的值开始(在这种情况下n为6)。 递减n并将其与之前的值相乘(因此这将变为6 * 5) 用分母进行除法然后递减(6 * 5/2,分母2变为1) 重复这些步骤,直到n小于最大2个分母并且在相同的迭代次数中除数(分母的最小值将变为1)

   int count(int n,int r)
   {int maxDen=r>(n-r)?r:n-r;      //larger number in the denominator
    int minDen=n-maxDen;           //the smaller number in denominator
    double num=1;
    for(int j=n;j>maxDen;j--)     
     {num=j*num;              //for C(6,4) example num=6*5 and so on   
    // System.out.println("num "+num +" minDen "+minDen);
       num=num/minDen;         //divide num 6*5 in this case by 2
       minDen--;
      }
   num=num%142875;           //output the result modulo 142875
  return (int) num;
}

但也许由于丢失精度,因为执行了更多的除法,它会给出错误的值但是它仍然为某些值提供正确的输出。因为它正确的是22 17而不是24 17。

(22 17) = 26334 //gives Correct value

(24 17)= 60353 //wrong value correct value is 60390

(25,17)=81450 //wrong value correct value is 81576

(16 15)= 16 //gives correct value

(87 28)= 54384 //wrong value correct value is 141525

我尝试使用num作为BigDecimal,因此我不得不用BigDecimal替换所有内容来执行操作。输出结果与上面代码中给出正确结果的输入相同。但是输入给出了错误的结果,该程序抛出异常

 Exception in thread "main" **java.lang.ArithmeticException: Non-terminating   decimal  expansion; no exact representable decimal result.**
at java.math.BigDecimal.divide(Unknown Source)
at Combination.NcRcount2.count(NcRcount2.java:16)
at Combination.NcRcount2.main(NcRcount2.java:37)

第16行是num = num.divide(minDen); //代替之前使用过的num / minDen,在这种情况下,num和minDen都是BigDecimal

即使数字没有精确的十进制表示,给定BigDecimal的任意精度,如果没有引发异常,结果中的错误也会被最小化。 ** 如果浮点数或双打的除法结果没有精确的十进制表示,那么为什么不抛出异常?**

我使用BigDecimal和动态编程方法验证了结果

   C(n,r)=C(n-1,r-1)+C(n-1,r)

这在所有情况下都能正常使用,因为它在我看来但必须有更好的方法

  BigDecimal Comb (int n, int k)
   {  if(k>n-k)
       k=n-k;
       BigDecimal B[][]=new BigDecimal[n+1] [k+1];

    for (int i = 0; i <= n; i++)
   { int min;
     if(i>=k)
      min=k;
    else
     min=i;
   for (int j = 0; j <= min; j++)
    { if (j == 0 || j == i)
           B[i][j] =new BigDecimal(1);
       else{ 
           if(j>i-j)
            B[i][j]=B[i][i-j];
            else
            B[i][j] = B[i - 1][j - 1].add(B[i - 1] [j]);
          }
    }
 }
BigDecimal div=new BigDecimal(142857);
return B[n][k].remainder(div);
}

请不要使用BigDecimal

,建议我更好的方法

4 个答案:

答案 0 :(得分:2)

public class Solution {

public static void main(String arg[]) {
    Scanner s = new Scanner(System.in);
    List<BigInteger> ar = new ArrayList<BigInteger>();
    int tot = Integer.parseInt(s.nextLine());
    BigInteger max = BigInteger.ZERO;
    for (int i = 0; i < tot; i++) {
        String str[] = s.nextLine().split(" ");
        Long n1 = Long.parseLong(str[0]);
        Long r1 = Long.parseLong(str[1]);
        Long nr1 = n1 - r1;
        BigInteger n = BigInteger.valueOf(n1);
        BigInteger r = BigInteger.valueOf(r1);
        BigInteger nr = BigInteger.valueOf(nr1);
        ar.add(n);
        ar.add(r);
        ar.add(nr);
        if (n.compareTo(max)==1) {
                max=n;
        }
        if (r.compareTo(max)==1) {
            max=r;
        }
        if (nr.compareTo(max)==1) {
            max=nr;
        }

    }
    HashMap<BigInteger,BigInteger> m=new HashMap<BigInteger,BigInteger>();
    m.put(BigInteger.ZERO, BigInteger.ONE);
    BigInteger fact=BigInteger.ONE;
for(BigInteger i=BigInteger.ONE;i.compareTo(max.add(BigInteger.ONE))==-1;i=i.add(BigInteger.ONE)){
    fact=fact.multiply(i);
    if(ar.contains(i)){
        m.put(i, fact);
    }
}

for(int i=0;i<ar.size();i=i+3){
    BigInteger n=m.get(ar.get(i));
    BigInteger r=m.get(ar.get(i+1));
    BigInteger nr=m.get(ar.get(i+2));
    BigInteger rem=r.multiply(nr);
    BigInteger act=n.divide(rem);
    BigInteger res=act.remainder(BigInteger.valueOf(142857));
    System.out.println(res);
}

}

}

我认为此代码可能会对您有所帮助。

答案 1 :(得分:0)

关于BigDecimal代码异常的问题部分我不清楚,所以我不会对此发表评论。

关于计算nCr的乘法和除法序列,wikipedia显示了一个易于实现的公式。问题的第一部分代码可能等同于它,可能是下面的python代码。它使用64位整数运算计算高达61C30; 62C31需要另外一两个。

def D(n, k):
    c, j, k = 1, n, min(k,n-k)
    for i in range(1,k+1):
        c, j = c*j/i, j-1
    return c

这个计算顺序的工作原因是,nC(j+1) = nCj * (n-j)/(j+1)可以从nCj = n!/j!(n-j)!和某些代数中轻松验证,所有除法都是精确的除法。也就是说,您可以在整数运算中完全计算大nCrn的{​​{1}},而无需任何小数位。

假设r。 注意,减少以K为模的中间项将导致问题并且可能是不可行的。如果分子缩小为mod K,则某些除法在普通算术中不准确。如果K是素数,则扩展的GCD算法可用于找到所有数的逆K k。但由于Bézout's lemma和一些模块化代数,K = 3 * 9 * 11 * 13 * 37且逆变量K将不存在3,11,13或37倍数的数字。

答案 2 :(得分:0)

你不应该分开。

在内存中绘制Pascal triangle。这只需要添加,并且很容易允许应用模运算。

此外,这将持续不长于分裂,因为您无法避免计算因子。

package tests.StackOverflow;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class q13241166 {

    public static void main(String[] args) throws IOException {

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        String s;
        String[] ss;
        int[] n;
        int[] r;
        int T;

        /*
        System.out.println("Input T:");
        s = in.readLine();
        T = Integer.parseInt(s);

        if( T < 1 || T > 100000) {
            throw new IllegalArgumentException();
        }
        */
        T = 9;

        /*
        n = new int[T];
        r = new int[T];

        System.out.println("Input n r pairs:");
        for(int i=0; i<T; ++i) {
            s = in.readLine();
            ss = s.split("\\s+");

            n[i] = Integer.parseInt(ss[0]);
            if( n[i] < 1 || n[i] > 1000000000) {
                throw new IllegalArgumentException();
            }

            r[i] = Integer.parseInt(ss[1]);
            if( r[i] < 0 || r[i] > n[i]) {
                throw new IllegalArgumentException();
            }

        }
        */
        n = new int[] {2, 4, 5, 10, 22, 24, 25, 16, 87};
        r = new int[] {1, 0, 2, 3, 17, 17, 17, 15, 28};


        int modulobase = 142857;
        int[] answers_old, answers = null;
        System.out.println("Output");
        for(int i=0; i<T; ++i) {
            for( int nn=0; nn<=n[i]; ++nn) {
                answers_old = answers;
                answers = new int[nn+1];
                for( int rr=0; rr<=nn; ++rr) {
                    if( rr == 0 || rr == nn ) {
                        answers[rr] = 1;
                    }
                    else {
                        answers[rr] = answers_old[rr-1] + answers_old[rr];
                    }

                    answers[rr] %= modulobase;
                }
            }

            System.out.println(answers[r[i]]);

        }



    }


}

输出如下:

Output
2
1
10
120
26334
60390
81576
16
141525

答案 3 :(得分:0)

相当直接的实施:

public long combinations(int n, int k) {
    BigInteger factorialN = factorial(n);
    BigInteger factorialK = factorial(k);
    BigInteger factorialNMinusK = factorial(n - k);
    return factorialN.divide(factorialK.multiply(factorialNMinusK)).longValue();;
}

private BigInteger factorial(int n) {
    BigInteger ret = BigInteger.ONE;
    for (int i = 1; i <= n; ++i) ret = ret.multiply(BigInteger.valueOf(i));
    return ret;
}