Matrix Exponentiation Explained — Fibonacci and Linear Recurrences in O(log n) [LC 509, Google, Stripe]
Advertisement
Algorithm Statement
Compute the -th term of a linear recurrence in time using matrix exponentiation.
Pack the last states into a column vector, multiply by a transition matrix , and raise to the -th power via fast exponentiation.
Constraints typical:
- (so per matrix multiply)
- Modulus
Example — Fibonacci:
F(n) = F(n-1) + F(n-2), F(0) = 0, F(1) = 1
[F(n+1)] [1 1]^n [F(1)]
[F(n) ] = [1 0] * [F(0)]
n = 10 → F(10) = 55Why This Problem Matters
Linear recurrences are everywhere: Fibonacci, Tribonacci, tilings, dice-roll counts, paths in regular graphs, partition counts, even some game-theory positions. A naive DP takes . For , that is impossible. Matrix exponentiation drops the cost to matrix multiplies, each costing .
Google asks "count strings over a small alphabet avoiding a substring" in , which reduces to matrix exponentiation on the KMP automaton. Stripe uses the technique to compute fee accumulations under recurring rules. Competitive programmers see it daily — it is the single most powerful "promote DP to log-time" trick in the interview canon.
The Core Insight
Any linear recurrence of order can be written as a vector update:
For Fibonacci ():
Iterating gives
The trick: matrix multiplication is associative, so can be computed by binary exponentiation on the matrix exponent. We square the matrix at each step, taking multiplies. Each multiply costs scalar operations.
Why it works. Linear recurrences correspond to discrete linear dynamical systems. The recurrence is a fixed linear map on the state vector; iterating it times is the -th power of that map.
General template. For a -th order recurrence :
The first row encodes the recurrence; the lower diagonal shifts the state.
Visual Dry Run
Compute with Fibonacci.
M = [[1, 1],
[1, 0]]
M^2 = [[2, 1],
[1, 1]]
M^4 = M^2 * M^2 = [[5, 3],
[3, 2]]
M^8 = M^4 * M^4 = [[34, 21],
[21, 13]]
M^9 = M^8 * M^1 = [[55, 34],
[34, 21]]
[F(10)] [55 34] [F(1)] [55 34] [1] [55]
[F(9) ] = [34 21] [F(0)] = [34 21] [0] = [34]
F(10) = 55. Correct (sequence: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55).For , we used 4 squarings (M^2, M^4, M^8) and 1 multiply (M^8 * M^1) = 5 matrix multiplies, equal to . Matches the binary expansion of 10 = 1010.
Solution (Optimal)
Python — Generic Matrix Exponentiation
MOD = 10**9 + 7
def mat_mul(A, B, mod=MOD):
n, k, m = len(A), len(B), len(B[0])
C = [[0] * m for _ in range(n)]
for i in range(n):
for j in range(m):
s = 0
for t in range(k):
s += A[i][t] * B[t][j]
C[i][j] = s % mod
return C
def mat_pow(M, p, mod=MOD):
n = len(M)
result = [[1 if i == j else 0 for j in range(n)] for i in range(n)]
while p > 0:
if p & 1:
result = mat_mul(result, M, mod)
M = mat_mul(M, M, mod)
p >>= 1
return result
def fibonacci(n: int) -> int:
if n == 0:
return 0
M = [[1, 1], [1, 0]]
R = mat_pow(M, n - 1)
# R * [F(1), F(0)]^T = [F(n), F(n-1)]^T → top entry
return R[0][0]Complexity: time, space.
JavaScript — Generic Matrix Exponentiation
const MOD = 1_000_000_007n;
function matMul(A, B, mod = MOD) {
const n = A.length, k = B.length, m = B[0].length;
const C = Array.from({ length: n }, () => new Array(m).fill(0n));
for (let i = 0; i < n; i++)
for (let j = 0; j < m; j++) {
let s = 0n;
for (let t = 0; t < k; t++) s += A[i][t] * B[t][j];
C[i][j] = s % mod;
}
return C;
}
function matPow(M, p, mod = MOD) {
const n = M.length;
let result = Array.from({ length: n }, (_, i) =>
Array.from({ length: n }, (_, j) => (i === j ? 1n : 0n))
);
while (p > 0n) {
if (p & 1n) result = matMul(result, M, mod);
M = matMul(M, M, mod);
p >>= 1n;
}
return result;
}
function fibonacci(n) {
if (n === 0n) return 0n;
const R = matPow([[1n, 1n], [1n, 0n]], n - 1n);
return R[0][0];
}Common Mistakes
- Wrong starting vector. Fibonacci needs as the initial vector, multiplied by . Off-by-one here gives instead of .
- Modulo on additions only. Accumulating products without modular reduction can overflow even
long longwhen is large. Mod inside the inner loop. - Identity matrix initialization. The "result" must start as the identity, not the zero matrix or the input matrix.
- Matrix multiplication is not commutative. Always multiply in the right order:
result = result * M, notM * result. - Confusing with . The mapping from to uses ; if you start from it is .
- Squaring inside
if p & 1branch. Square unconditionally each step, multiply only when the bit is set. - Allocating new matrices unnecessarily. For very tight contests, in-place multiplication into a temp buffer saves time.
Interview Tips
- Spell out the transition: "I will represent the state as a vector and the transition as a matrix." This signals depth.
- Mention complexity in terms of : "Each matrix multiply is , and we do of them."
- For non-Fibonacci recurrences, draw the matrix on the whiteboard. Interviewers want to see you place coefficients correctly.
- If the recurrence has a forcing term (a non-homogeneous part), append a constant column to the state — a classic Stripe interview twist.
Follow-up Questions
Q1: Solve Tribonacci for . A: transition matrix with the first row and a identity below. Same exponentiation technique.
Q2: Count walks of length in a graph with adjacency matrix . A: (A^n)_{i,j} counts walks from to of length exactly . Use matrix exponentiation.
Q3: Add a non-homogeneous term: . A: Augment the state to and use .
Q4: Why is matrix exponentiation and not ? A: Standard matrix multiply costs . Strassen reduces to , but the constant overhead rarely pays off for small .
Key Takeaways
- Any linear recurrence of order becomes a matrix-vector update with a transition matrix.
- Binary exponentiation on the matrix computes in — fast enough for .
- For Fibonacci, the transition is , and is the top-left entry of .
- Non-homogeneous terms are absorbed by appending a constant slot to the state vector and a corresponding row in .
- The technique applies far beyond integer recurrences: count walks in graphs, paths in automata, transitions in Markov chains.
- Always reduce inside the inner-product loop to avoid 64-bit overflow when entries are large.
Advertisement