Combinatorics nCr Modulo Prime Explained — Pascal Triangle, Factorial Inverse, Lucas Theorem [LC 62, Google, Meta]
Advertisement
Algorithm Statement
Compute the binomial coefficient , often modulo a prime . Three regimes:
- Small (up to a few thousand): Pascal's triangle in .
- Medium (up to ) with prime modulus: precompute factorials and inverse factorials in , then answer each query in .
- Huge (up to ) with small prime modulus: Lucas's theorem reduces to base- digits in .
Constraints typical in interviews:
- and .
- Modulus is usually (prime).
Example:
n = 5, r = 2, p = 10^9 + 7
C(5, 2) = 5! / (2! * 3!) = 120 / 12 = 10
Output: 10Why This Problem Matters
Counting problems dominate the medium and hard tiers of every interview platform. LC 62 Unique Paths asks for . LC 1922 Count Good Numbers asks for but extends to factorial-based variants. LC 1359 Count All Valid Pickup and Delivery Options is the central binomial coefficient times factorials. Google and Meta interviewers love these because they reward candidates who recognise the closed form, then compute it correctly under modular arithmetic without overflow.
The mistake I see most often: candidates know the formula but compute factorials naively, forget modular inverse, or use Fermat with a composite modulus. This blog cleans that up.
The Core Insight
Three approaches scale differently:
Pascal's triangle. Use the recurrence with base cases . Build a 2D DP table in time and or space. Works for any modulus, prime or not.
Factorial precomputation. When the modulus is prime, compute fact[i] for in . Then . The two inverse factorials come from Fermat's little theorem: . A clever trick computes all inverse factorials in by inverting only and walking backwards: inv_fact[i] = inv_fact[i+1] * (i+1) mod p.
Lucas's theorem. For up to with a small prime , write and in base :
Then
Each has , computable directly. Total time .
Why the inverse-factorial trick works. From we get . Compute the largest inverse with one power call, then everything else with multiplications.
Visual Dry Run
Compute using factorial precomputation.
fact[0..6] = [1, 1, 2, 6, 24, 120, 720]
inv_fact[6] = power(720, MOD - 2, MOD) → some big value F6
inv_fact[5] = inv_fact[6] * 6
inv_fact[4] = inv_fact[5] * 5
inv_fact[3] = inv_fact[4] * 4
inv_fact[2] = inv_fact[3] * 3
C(6, 2) = fact[6] * inv_fact[2] * inv_fact[4] mod p
= 720 * (1/2) * (1/24) mod p
= 720 / 48 = 15Verify with Pascal: . Correct.
For Lucas, compute . Base 7: and .
Digits: n = [2, 6, 2, 6] r = [0, 6, 0, 6]
C(2,0) * C(6,6) * C(2,0) * C(6,6)
= 1 * 1 * 1 * 1 = 1
So C(1000, 300) mod 7 = 1.Solution (Optimal)
Python — Factorial Precomputation
MOD = 10**9 + 7
N = 10**6 + 5
fact = [1] * N
inv_fact = [1] * N
def precompute():
for i in range(1, N):
fact[i] = fact[i - 1] * i % MOD
inv_fact[N - 1] = pow(fact[N - 1], MOD - 2, MOD)
for i in range(N - 2, -1, -1):
inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD
def nCr(n: int, r: int) -> int:
if r < 0 or r > n:
return 0
return fact[n] * inv_fact[r] % MOD * inv_fact[n - r] % MOD
precompute()Complexity: precompute, per query, memory.
Python — Lucas's Theorem
def nCr_small(n: int, r: int, p: int) -> int:
if r > n: return 0
num = den = 1
for i in range(r):
num = num * ((n - i) % p) % p
den = den * (i + 1) % p
return num * pow(den, p - 2, p) % p
def lucas(n: int, r: int, p: int) -> int:
result = 1
while n > 0 or r > 0:
ni, ri = n % p, r % p
if ri > ni:
return 0
result = result * nCr_small(ni, ri, p) % p
n //= p
r //= p
return resultComplexity: per query.
JavaScript — Pascal's Triangle (small n)
function pascalsTriangle(n) {
const C = Array.from({ length: n + 1 }, (_, i) =>
new Array(i + 1).fill(1n)
);
for (let i = 2; i <= n; i++) {
for (let j = 1; j < i; j++) {
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
}
return C;
}JavaScript — Factorial Precomputation
const MOD = 1_000_000_007n;
const N = 1_000_005;
const fact = new Array(N).fill(1n);
const invFact = new Array(N).fill(1n);
function power(a, n, m) {
a %= m; let r = 1n;
while (n > 0n) {
if (n & 1n) r = r * a % m;
a = a * a % m; n >>= 1n;
}
return r;
}
for (let i = 1; i < N; i++) fact[i] = fact[i - 1] * BigInt(i) % MOD;
invFact[N - 1] = power(fact[N - 1], MOD - 2n, MOD);
for (let i = N - 2; i >= 0; i--)
invFact[i] = invFact[i + 1] * BigInt(i + 1) % MOD;
function nCr(n, r) {
if (r < 0 || r > n) return 0n;
return fact[n] * invFact[r] % MOD * invFact[n - r] % MOD;
}Common Mistakes
- Computing factorials without modulo. overflows long before you finish. Apply
% MODafter every multiplication. - Dividing instead of multiplying by inverse. Modular arithmetic does not support division. Use the inverse from Fermat or Extended Euclidean.
- Wrong base case in Pascal. , and when or .
- Using Fermat with a composite modulus. Fermat requires prime. For composite, fall back to Lucas's generalisation or precompute via Pascal.
- Building Pascal's triangle when is large. A table is impossible; switch to factorial precomputation.
- Lucas with at any digit. The base- digit comparison must short-circuit to 0 the moment some .
- JavaScript Number precision. All factorials must be BigInt; otherwise silently corrupts.
Interview Tips
- Lead with the formula: "I will precompute factorials and inverse factorials in , then answer each in ." This signals fluency.
- Mention the inverse-factorial trick: only one
powercall total, the rest is a backward sweep. - If is large but the modulus is small, pivot to Lucas — never try to factorial .
- For combinatorics on grids (paths) and lattices (Catalan), state the closed form before any code.
Follow-up Questions
Q1: Compute when the modulus is not prime, like . A: Use Pascal's triangle, or factor into prime powers and apply Lucas's generalisation plus the Chinese Remainder Theorem.
Q2: Compute the central binomial coefficient for . A: Same factorial precomputation; the answer is .
Q3: Compute Catalan number . A: Compute as above, then multiply by the modular inverse of .
Q4: Why is Lucas's theorem useful only for small primes? A: Lucas needs to compute for digits . If is too big, the inner factorial precomputation does not fit in memory.
Key Takeaways
- has three computational regimes: Pascal for tiny , factorial precomputation for , Lucas for up to .
- Factorial precomputation runs queries in after setup, using one
powercall and a backward sweep for inverse factorials. - Fermat's little theorem turns modular division into modular exponentiation: .
- Lucas's theorem factors the binomial coefficient over the base- digits — essential for giant arguments with a small prime modulus.
- Always guard the boundary cases r < 0, , and before indexing into the factorial table.
- For composite moduli, fall back to Pascal's triangle or Lucas's generalisation paired with the Chinese Remainder Theorem.
Advertisement