Time for a touch of Maths:
nCr = fact(n)/(fact(r)*fact(n-r))
An example makes this easier, let's do 5C3:
5C3 = fact(5)/(fact(3)*fact(5-3))
= fact(5)/(fact(3)*fact(2))
= 5x4x3x2x1 / (3x2x1 * 2x1)
= 5x4 / 2x1
So we can see that some of the factors will cancel; that is very useful if the full fact(n)
would overflow.
Define a range factorial:
rafa(a,b) = a*(a-1)*...*(b+1)*b where a > b
= product(b..a)
Then
rafa(n,r) = fact(n)/fact(r)
-> nCr = rafa(n,r) / fact(r)
We could stop here, and we would have significantly broadened the set of values of n
and r
for which we can calculate values without overflowing.
Extra points
With rafa(n,r)
and fact(r)
, we can see that when r
is nearly as large as n
, then most of the scale of the problem will cancel. So 100C97 = 100x99x98 / 3x2x1 = 100x33x49.
Consider, then, a factor-matching algorithm (psuedo-code rather like python):
numerElems = [100,99,98]
initialDenomElems = [3,2,1]
# cancelFactors modifies numerElems values, returns un-cancelled denominator elements
def cancelFactors(numerElems, denomElems):
finalDenomElems = []
for denom in denomElems:
for factor in factorise(denom):
for idx,numer in enumerate(numerElems):
if isFactorOf(factor, numer):
numerElems[idx] = numer/factor
else
finalDenomElems.push(factor)
return finalDenomElems;
You can then perform the final calculation of the product of the numerator elements divided by the product of the remaining denominator elements. Since we know nCr always returns an integer result, we know that when cancelFactors is used it will always cancel all the factors. Thus this algorithm maximises the possible space of n,r pairs which do not overflow. However, as written, it is O(n^3 * f), where f
is the cost of getting all the factors of an integer, so it will not be fast. For large values of n and r, though, it may be the only way to calculate the result without using different types.