java库中是否有内置方法可以为任何N,R计算'N选R'?
答案 0 :(得分:96)
实际上,即使没有计算因子,也很容易计算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!
因此,这为我们提供了一个简单的起点,使用上面的公式,我们可以找到(N choose K)
K > 0
K
乘法和K
除法。
将上述内容放在一起,我们可以轻松生成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"
谷歌称,133 choose 71 = 5.55687037 × 1038。
答案 1 :(得分:44)
apache-commons“Math”支持这个 org.apache.commons.math4.util.CombinatoricsUtils
答案 2 :(得分:20)
recursive definition为您提供了一个非常简单的选择功能,可以很好地处理小值。如果您计划大量运行此方法,或者使用较大的值,则需要记住它,但是否则可以正常运行。
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);
}
改进此功能的运行时间保留为exercise for the reader:)
答案 3 :(得分:13)
我只想计算不同套牌尺寸的2张牌组合的数量......
无需导入外部库 - 从组合定义中导入n
卡n*(n-1)/2
奖金问题:这个相同的公式计算出第一个n-1
整数的总和 - 你知道为什么它们是相同的吗? :)
答案 4 :(得分:4)
N!/((R 1)(N-R)!)
有很多你可以在这个公式中取消,所以通常,阶乘是没有问题的。假设R&gt; (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
}
重要的是用一个开始划分,尽管这似乎是多余的。但是让我们举个例子:
for N = 6, R = 2: 6!/(2!*4!) => 5*6/(1*2)
如果我们离开1,我们将计算5/2 * 6。除法将离开整数域。离开1我们保证我们不这样做,因为乘法的第一个或第二个操作数是偶数。
出于同样的原因,我们不使用r *= (m/d)
。
整个事情可以修改为
r := max(R, N-R)+1
for (m := r+1,d := 2; m <= N; m++, d++ ) {
r *= m
r /= d
}
答案 5 :(得分:4)
这个数学公式是:
N!/((R!)(N-R)!)
不应该很难从那里弄明白:)
答案 6 :(得分:3)
答案 7 :(得分:2)
以下例程将使用递归定义和memoization计算n-choose-k。该例程非常快速准确:
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;
}
答案 8 :(得分:1)
答案 9 :(得分:1)
ArithmeticUtils.factorial
现在显然已弃用了。请尝试CombinatoricsUtils.binomialCoefficientDouble(n,r)
答案 10 :(得分:0)
与番石榴版本类似,Richard J. Mathar提到的BigIntegerMath类here称为org.nevec.rjm,它是类的包。
它们的实现为二项式方法提供了两个签名:int,int和BigInteger,BigInteger。
答案 11 :(得分:0)
使用hashmap改进@ 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);
}
答案 12 :(得分:0)
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);
}
答案 13 :(得分:0)
根据公式:n!/((n-k)!* k!) 如果我们简单地计算分子和分母,那么许多计算将被浪费,并且可能填充“int”,“float”或甚至“BigInteger”的范围。 因此,为了克服这种情况,我们可以在将值乘以之前取消所有内容。
假设n = 6,k = 3
是=&gt; 6 * 5 * 4 * 3 * 2 * 1 /((3 * 2)*(3 * 2))
假设如果我们乘以分子,则范围可以填充。更好的选择是在将值乘以之前取消它。
在这种情况下 - &gt; 如果我们取消所剩下的所有内容: (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++;
}
}
答案 14 :(得分:0)
已经提交了许多解决方案。
某些解决方案未考虑整数溢出。
某些溶液在给定n和r的情况下计算了所有可能的nCr。 结果是需要更多的时间和空间。
在大多数情况下,我们需要直接计算nCr。我将再分享一个解决方案。
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;
}
答案 15 :(得分:0)
我们可以利用以下事实来代替n递归地选择k(当选择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);
}
请注意,上面的select方法假定n和k都不为负。此外,长数据类型可能会溢出以获得足够大的值。如果结果有问题,则应使用BigInteger版本。分子和/或分母应超过64位。
答案 16 :(得分:-1)
public static long nCr(int n, int r) {
long a = n;
long b = r;
long c = (n - r);
for (int o = (int)a - 1; o > 0; o--) { a = a * o; }
for (int o = (int)b - 1; o > 0; o--) { b = b * o; }
for (int o = (int)c - 1; o > 0; o--) { c = c * o; }
return (a / (b * c)); // n! / r! * (n - r)!
}
从几年前的答案编辑,其中a,b和c是整数,整数溢出使得该方法严重无法使用。这个在可靠性方面并不是更好,但它很懒惰。
如果价值超过了长期的限制,这也将是砖块......除非你试图为学校项目找到一些快速的解决方案,否则不太可行。