2

这是来自 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

4 回答 4

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

}

}

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

于 2013-12-25T20:58:52.497 回答
1

相当简单的实现:

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;
}
于 2014-08-02T21:32:07.393 回答
0

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

关于计算 nCr 的乘法和除法序列,维基百科显示了一个易于实现的公式。您在问题中的第一部分代码可能与它等效,就像下面的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)!一些代数中验证。也就是说,您可以在不需要任何小数位的情况下完全以整数算术进行nCr大计算。nr

假设K=142857。请注意,以 K 为模减少中间项会导致问题并且可能不可行。如果分子减少 mod K,则某些除法在普通算术中将不准确。如果 K 是素数,则可以使用扩展的 GCD 算法来找到所有数字的逆模 K。但是,由于Bézout 引理和一些模代数,对于 3、11、13或 37 的倍数的数字,K=3*9*11*13*37 和反模 K 将不存在。

于 2012-11-06T03:12:39.363 回答
0

你不应该分裂。

在内存中绘制帕斯卡三角形。这将只需要加法,并且很容易允许应用模运算。

此外,这不会比除法持续更长时间,因为您无法避免计算阶乘。

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
于 2013-12-25T23:20:23.273 回答