Topics
When computing binomial coefficient (permutations and combinations), we have two primary approaches:
- Dynamic Programming Approach - Uses the recursive relation:
- Time Complexity:
- Space Complexity: (can be optimized to )
- Factorial-Based Approach - Uses the formula:
- Time Complexity: (for factorials)
- Space Complexity:
The factorial-based approach is significantly faster than DP. However, factorials grow exponentially, so we compute results modulo a prime m
, typically .
Modular Arithmetic for Efficient Computation
Since direct division is not possible in modular arithmetic, we compute the modular multiplicative inverse of the denominator using fermat’s little theorem if m
is prime:
- Fermat’s Theorem:
- Rearranged for modular inverse:
- Computed efficiently using binary exponentiation
Binary Exponentiation for Fast Powering
def binpow(a, b, m): # Computes (a^b) % m efficiently
a %= m
res = 1%m
while b > 0:
if b & 1:
res = (res * a) % m
a = (a * a) % m
b >>= 1
return res
Factorial and Modular Inverse Calculation
def factorial(n, m): # Computes n! % m
res = 1
for i in range(1, n + 1):
res = (res * i) % m
return res
def modinv(n, m): # Computes modular inverse using Fermat's Theorem
return binpow(n, m - 2, m)
- Time complexity:
- Space complexity:
Computing Permutations and Combinations Efficiently
def permutations(n, r, m):
num = factorial(n, m)
den = factorial(n - r, m)
inv_den = modinv(den, m)
return (num * inv_den) % m
def combinations(n, r, m):
num = factorial(n, m)
den1 = factorial(n - r, m)
den2 = factorial(r, m)
inv_den1 = modinv(den1, m)
inv_den2 = modinv(den2, m)
return ((num * inv_den2) % m * inv_den1) % m
- Time Complexity:
- Space Complexity:
Handling Non-Prime Moduli using Extended Euclidean Algorithm
If is not prime, Fermat’s theorem is invalid. Instead, we use the extended euclidean algorithm to compute the modular inverse:
def extended_gcd(a, b):
if a == 0:
return (b, 0, 1)
d, x1, y1 = extended_gcd(b % a, a)
x = y1 - (b // a) * x1
y = x1
return (d, x, y)
def modinv_non_prime(a, m):
g, x, y = extended_gcd(a, m)
if g != 1:
raise Exception("Modular inverse does not exist") # a and m must be coprime
else:
return (x % m + m) % m # Ensure positive result
- Time Complexity:
Optimized Approach using Precomputed Factorials and Inverses
To answer multiple queries efficiently, we precomputing factorials and inverse factorials up to :
def precompute_factorials(n, m):
fact = [1] * (n + 1)
inv_fact = [1] * (n + 1)
for i in range(2, n + 1):
fact[i] = (fact[i-1] * i) % m
inv_fact[n] = modinv(fact[n], m) # Compute inverse of n! first
for i in range(n-1, 0, -1):
inv_fact[i] = (inv_fact[i + 1] * (i + 1)) % m
return fact, inv_fact
def comb_precomputed(n, r, m, fact, inv_fact):
return ((fact[n] * inv_fact[r]) % m * inv_fact[n - r]) % m
- Precomputation Time Complexity:
- Query Time Complexity:
- Space Complexity: