Math
Express a linear recurrence as matrix multiplication, then compute the n-th term in O(k³ log n) via repeated squaring. Turns O(n) DP into O(log n) when n is huge and the state space k is small.
Many dynamic programming recurrences follow a simple pattern: the next value depends on a fixed number of previous values. When the number of steps n is small, a straightforward loop works fine. But when reaches or beyond, even an loop is too slow. Matrix exponentiation transforms any constant-coefficient linear recurrence into a matrix power problem, solvable in where is the number of terms in the recurrence.
The Fibonacci recurrence is the simplest case:
F(n) = F(n-1) + F(n-2), F(0) = 0, F(1) = 1We can rewrite this as a matrix equation. At each step, we transform the state vector into :
| F(n+1) | | 1 1 | | F(n) |
| F(n) | = | 1 0 | * | F(n-1) |Call the matrix . Applying this transformation times from the base case gives:
| F(n+1) | | 1 |
| F(n) | = T^n | 0 |So is the bottom-left entry (row 1, column 0) of . Computing via repeated squaring takes matrix multiplications, each costing scalar operations. The total is , down from .
Let . We compute by repeated squaring. Since , we need .
Step 1: Start with result = I (identity matrix) and base = T. .
Step 2: is odd. result = I * T = T = [[1,1],[1,0]]. Set , square base: .
Step 3: is even. Skip multiply. Set , square base: .
Step 4: is even. Skip multiply. Set , square base: (not needed).
Step 5: is odd. result = T * T^4 = T^5.
T^5 = [[1,1],[1,0]] * [[5,3],[3,2]] = [[8,5],[5,3]]Reading from position (or equivalently ): . Correct.
Any linear recurrence of the form:
f(n) = c1*f(n-1) + c2*f(n-2) + ... + ck*f(n-k)can be represented as a transition matrix :
| f(n) | | c1 c2 c3 ... ck | | f(n-1) |
| f(n-1) | | 1 0 0 ... 0 | | f(n-2) |
| f(n-2) | = | 0 1 0 ... 0 | * | f(n-3) |
| ... | | ... | | ... |
| f(n-k+1)| | 0 0 0 ... 0 | | f(n-k) |The first row holds the coefficients. Each subsequent row shifts the state down by one position. Computing via binary exponentiation gives in time.
The code consists of three pieces: matrix multiplication, matrix power via repeated squaring, and the recurrence solver.
MOD = 10**9 + 7
def mat_mul(A, B):
"""Multiply two k x k matrices mod MOD."""
k = len(A)
C = [[0] * k for _ in range(k)]
for i in range(k):
for j in range(k):
for p in range(k):
C[i][j] = (C[i][j] + A[i][p] * B[p][j]) % MOD
return C
def mat_pow(M, n):
"""Compute M^n mod MOD using binary exponentiation."""
k = len(M)
result = [[int(i == j) for j in range(k)] for i in range(k)] # identity
while n > 0:
if n & 1:
result = mat_mul(result, M)
M = mat_mul(M, M)
n >>= 1
return result
def fibonacci(n):
if n <= 1:
return n
T = [[1, 1],
[1, 0]]
return mat_pow(T, n)[0][1]The key insight in mat_pow is that it is identical to scalar binary exponentiation. The only difference is that scalar multiplication is replaced by matrix multiplication. Because matrix multiplication is associative, the repeated-squaring identity holds.
A chess knight is placed on a phone dial pad. Starting from any digit, it hops times, each hop following an L-shaped knight move. Count how many distinct -digit numbers can be dialed.
The phone pad layout determines which digits a knight can reach from each position:
| 1 | 2 | 3 |
| 4 | 5 | 6 |
| 7 | 8 | 9 |
| | 0 | |The valid knight moves define a transition graph:
We build a transition matrix where if a knight on digit can hop to digit . Then the number of -digit numbers is the sum of all entries in , where (we can start on any digit).
def knight_dialer(n):
if n == 1:
return 10
moves = {
0: [4, 6], 1: [6, 8], 2: [7, 9],
3: [4, 8], 4: [0, 3, 9], 5: [],
6: [0, 1, 7], 7: [2, 6], 8: [1, 3],
9: [2, 4]
}
# Build 10x10 transition matrix
T = [[0] * 10 for _ in range(10)]
for src, dsts in moves.items():
for dst in dsts:
T[dst][src] = 1 # column src -> row dst
Tn = mat_pow(T, n - 1)
# Sum all entries: each starting digit contributes
# sum of its column in T^(n-1)
total = 0
for i in range(10):
for j in range(10):
total = (total + Tn[i][j]) % MOD
return totalWithout matrix exponentiation, you would use DP in time. When can be , that is far too slow. Matrix exponentiation solves it in operations.
When applying this technique to a new problem:
This technique shines under specific conditions:
Common problem types: counting paths of length in a graph with nodes, linear recurrence evaluation, string counting with fixed-length forbidden patterns (via automaton states).
For LeetCode 509. Fibonacci Number, , so the time is . For LeetCode 935. Knight Dialer, , so each multiplication is and the total is .
The tradeoff is clear: matrix exponentiation replaces an loop with . When is small and is enormous, this is a dramatic win. When is large or is small, the overhead of matrix multiplication makes the naive loop faster.
You're probably looking at matrix exponentiation when:
Common templates: