Javaライブラリに組み込みメソッドがあり、任意のN、Rに対して 'N choose R'を計算できますか?
Apache-commons "Math"は org.Apache.commons.math4.util.CombinatoricsUtils でこれをサポートします
実際、階乗を計算することなくN choose K
を計算するのは非常に簡単です。
(N choose K)
の式は次のとおりです。
N!
--------
(N-K)!K!
したがって、(N choose K+1)
の式は次のとおりです。
N! N! N! N! (N-K)
---------------- = --------------- = -------------------- = -------- x -----
(N-(K+1))!(K+1)! (N-K-1)! (K+1)! (N-K)!/(N-K) K!(K+1) (N-K)!K! (K+1)
あれは:
(N choose K+1) = (N choose K) * (N-K)/(K+1)
また、(N choose 0)
が以下であることもわかっています。
N!
---- = 1
N!0!
したがって、これは簡単な出発点を提供し、上記の式を使用して、K
乗算とK
除算を持つ(N choose K)
のK > 0
を見つけることができます。
上記をまとめると、次のようにPascalの三角形を簡単に生成できます。
for (int n = 0; n < 10; n++) {
int nCk = 1;
for (int k = 0; k <= n; k++) {
System.out.print(nCk + " ");
nCk = nCk * (n-k) / (k+1);
}
System.out.println();
}
これは印刷します:
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
1 9 36 84 126 126 84 36 9 1
BigInteger
バージョンBigInteger
の式の適用は簡単です。
static BigInteger binomial(final int N, final int K) {
BigInteger ret = BigInteger.ONE;
for (int k = 0; k < K; k++) {
ret = ret.multiply(BigInteger.valueOf(N-k))
.divide(BigInteger.valueOf(k+1));
}
return ret;
}
//...
System.out.println(binomial(133, 71));
// prints "555687036928510235891585199545206017600"
Googleによると、 133を選択71 = 5.55687037×1038 。
再帰定義 を使用すると、小さな値に対して適切に機能する非常に単純な選択関数が提供されます。このメソッドを大量に、または大きな値で実行することを計画している場合は、それをメモするのに費用がかかりますが、それ以外の場合は正常に機能します。
public static long choose(long total, long choose){
if(total < choose)
return 0;
if(choose == 0 || choose == total)
return 1;
return choose(total-1,choose-1)+choose(total-1,choose);
}
この関数の実行時間の改善は 読者の運動 :)として残されています
私は異なるデッキサイズで2枚のカードの組み合わせの数を計算しようとしています...
外部ライブラリをインポートする必要はありません-n
カードとの組み合わせの定義から、n*(n-1)/2
になります
ボーナス質問:この同じ式は、最初のn-1
整数-なぜ同じなのかわかりますか? :)
この数式は次のとおりです。
N!/((R!)(N-R)!)
そこから理解するのは難しくないはずです:)
N!/((R!)(N-R)!)
この式にはキャンセルできるものがたくさんあるので、通常、階乗は問題ありません。 R>(N-R)その後、N!/ R!をキャンセルするとしましょう。 to(R + 1)*(R + 2)* ... * N.しかし、本当、intは非常に限られています(約13!)。
しかし、その後、反復ごとに分割することもできます。擬似コードで:
_d := 1
r := 1
m := max(R, N-R)+1
for (; m <= N; m++, d++ ) {
r *= m
r /= d
}
_
これは不必要に思えますが、1つで分割を開始することが重要です。しかし、例を作りましょう:
_for N = 6, R = 2: 6!/(2!*4!) => 5*6/(1*2)
_
1を省略すると、5/2 * 6が計算されます。除算は整数領域を離れます。 1のままにしておくと、乗算の第1オペランドまたは第2オペランドが偶数であるため、これを行わないことが保証されます。
同じ理由で、r *= (m/d)
は使用しません。
全体を修正することができます
_r := max(R, N-R)+1
for (m := r+1,d := 2; m <= N; m++, d++ ) {
r *= m
r /= d
}
_
次のルーチンは、再帰的な定義とメモ化を使用して、n-choose-kを計算します。ルーチンはextremely高速かつ正確です:
inline unsigned long long n_choose_k(const unsigned long long& n,
const unsigned long long& k)
{
if (n < k) return 0;
if (0 == n) return 0;
if (0 == k) return 1;
if (n == k) return 1;
if (1 == k) return n;
typedef unsigned long long value_type;
value_type* table = new value_type[static_cast<std::size_t>(n * n)];
std::fill_n(table,n * n,0);
class n_choose_k_impl
{
public:
n_choose_k_impl(value_type* table,const value_type& dimension)
: table_(table),
dimension_(dimension)
{}
inline value_type& lookup(const value_type& n, const value_type& k)
{
return table_[dimension_ * n + k];
}
inline value_type compute(const value_type& n, const value_type& k)
{
if ((0 == k) || (k == n))
return 1;
value_type v1 = lookup(n - 1,k - 1);
if (0 == v1)
v1 = lookup(n - 1,k - 1) = compute(n - 1,k - 1);
value_type v2 = lookup(n - 1,k);
if (0 == v2)
v2 = lookup(n - 1,k) = compute(n - 1,k);
return v1 + v2;
}
value_type* table_;
value_type dimension_;
};
value_type result = n_choose_k_impl(table,n).compute(n,k);
delete [] table;
return result;
}
_ArithmeticUtils.factorial
_は明らかに非推奨になりました。 CombinatoricsUtils.binomialCoefficientDouble(n,r)
をお試しください
Nを実装する代わりに、再帰的にkを選択します(大きな数値では遅くなる可能性があります)。
n(n-1)(n-2)...(n-k+1)
n choose k = --------------------
k!
それでもk!を計算する必要がありますが、これは再帰的な方法よりもはるかに高速に実行できます。
private static long choose(long n, long k) {
long numerator = 1;
long denominator = 1;
for (long i = n; i >= (n - k + 1); i--) {
numerator *= i;
}
for (long i = k; i >= 1; i--) {
denominator *= i;
}
return (numerator / denominator);
}
上記のchooseメソッドは、nもkも負ではないと想定していることに注意してください。また、十分な大きさの値に対してlongデータ型がオーバーフローする可能性があります。結果がそれぞれの場合、BigIntegerバージョンを使用する必要があります。分子や分母は64ビットを超えると予想されます。
すでに多くの解決策が提出されています。
整数オーバーフローを考慮しないソリューションもありました。
一部のソリューションでは、nとrを指定して、可能なすべてのnCrを計算しました。結果として、より多くの時間とスペースが必要になります。
ほとんどの場合、nCrを直接計算する必要があります。もう1つのソリューションを共有します。
static long gcd(long a, long b) {
if (a == 0) return b;
return gcd(b%a, a);
}
// Compute (a^n) % m
static long bigMod(long a, long n, long m) {
if (n == 0) return 1;
if (n == 1) return a % m;
long ret = bigMod(a, n/2, m);
ret = (ret * ret) % m;
if (n % 2 == 1) return (ret * a) % m;
return ret;
}
// Function to find (1/a mod m).
// This function can find mod inverse if m are prime
static long modInverseFarmetsTheorem(long a, long m) {
if (gcd(a, m) != 1) return -1;
return bigMod(a, m-2, m);
}
// This function finds ncr using modular multiplicative inverse
static long ncr(long n, long r, long m) {
if (n == r) return 1;
if (r == 1) return n;
long start = n - Math.max(r, n - r) + 1;
long ret = 1;
for (long i = start; i <= n; i++) ret = (ret * i) % m;
long until = Math.min(r, n - r), denom = 1;
for (long i = 1; i <= until; i++) denom = (denom * i) % m;
ret = (ret * modInverseFarmetsTheorem(denom, m)) % m;
return ret;
}
public static void combinationNcK(List<String> inputList, String prefix, int chooseCount, List<String> resultList) {
if (chooseCount == 0)
resultList.add(prefix);
else {
for (int i = 0; i < inputList.size(); i++)
combinationNcK(inputList.subList(i + 1, inputList.size()), prefix + "," + inputList.get(i), chooseCount - 1, resultList);
// Finally print once all combinations are done
if(prefix.equalsIgnoreCase("")){
resultList.stream().map(str->str.substring(1)).forEach(System.out::println);
}
}
}
public static void main(String[] args) {
List<String> positions = Arrays.asList(new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" });
List<String> resultList = new ArrayList<String>();
combinationNcK(positions, "", 3, resultList);
}
N!/((n-k)!* k!)分子と分母を単純に計算すると、多くの計算が無駄になり、おそらく「int」、「float」、さらには「BigInteger」の範囲がいっぱいになる可能性があります。したがって、このシナリオを克服するために、値を乗算する前に物事をキャンセルできます。
n = 6、k = 3と仮定します
=> 6 * 5 * 4 * 3 * 2 * 1 /((3 * 2)*(3 * 2))
分子を掛けると、範囲が満たされると仮定します。より良いオプションは、値を乗算する前にキャンセルすることです。
この場合->すべてをキャンセルすると、次のようになります:(2 * 5 * 2)
これらの値の乗算ははるかに簡単で、必要な計算が少なくなります。
================================================== ====
以下のコードは、次の場合に数字に対して「効率的に」機能します。
おそらく、コードは引き続き改善できます。
BigInteger calculateCombination(int num, int k) {
if (num == k || k == 0)
return BigInteger.ONE ;
int numMinusK = num - k;
int stopAt; // if n=100, k=2 , can stop the multiplication process at 100*99
int denominator;
// if n=100, k=98 OR n=100, k=2 --> output remains same.
// thus choosing the smaller number to multiply with
if (numMinusK > k) {
stopAt = numMinusK;
denominator = k;
} else {
stopAt = k;
denominator = numMinusK;
}
// adding all the denominator nums into list
List<Integer> denoFactList = new ArrayList<Integer>();
for (int i = 2; i <= denominator; i++) {
denoFactList.add(i);
}
// creating multiples list, because 42 / 27 is not possible
// but 42 / 3 and followed by 42 / 2 is also possible
// leaving us only with "7"
List<Integer> multiplesList = breakInMultiples(denoFactList);
Collections.sort(multiplesList, Collections.reverseOrder());
Iterator<Integer> itr;
BigInteger total = BigInteger.ONE;
while (num > 0 && num > stopAt) {
long numToMultiplyWith = num;
if (!multiplesList.isEmpty()) {
itr = multiplesList.iterator();
while (itr.hasNext()) {
int val = itr.next();
if (numToMultiplyWith % val == 0) {
numToMultiplyWith = numToMultiplyWith / val;
itr.remove();
}
}
}
total = total.multiply(BigInteger.valueOf(numToMultiplyWith));
num--;
}
return total;
}
ArrayList<Integer> breakInMultiples(List<Integer> denoFactList) {
ArrayList<Integer> multiplesList = new ArrayList<>();
for (int i : denoFactList)
updateListWithMultiplesOf(multiplesList, i);
return multiplesList;
}
void updateListWithMultiplesOf(ArrayList<Integer> list, int i) {
int count = 2;
while (i > 1) {
while (i % count == 0) {
list.add(count);
i = i / count;
}
count++;
}
}
ハッシュマップを使用して@ dimo414のソリューションを改善する:
private static Map<Integer, Map<Integer, Integer>> map = new HashMap<>();
private static int choose(int total, int choose){
if(total < choose)
return 0;
if(choose == 0 || choose == total)
return 1;
if (! (map.containsKey(total) && map.get(total).containsKey(choose))){
map.put(total, new HashMap<>());
map.get(total).put(choose, choose(total-1,choose-1)+choose(total-1,choose));
}
return map.get(total).get(choose);
}
Guavaバージョンと同様に、BigIntegerMathクラスがあります ここで Richard J. Matharによるorg.nevec.rjmと呼ばれるクラスのパッケージです。
それらの実装は、二項メソッドに2つのシグネチャ、int、intおよびBigInteger、BigIntegerを提供します。