Skip to content

Commit 53a30c4

Browse files
author
Akibuzzaman Akib
committed
maths: add O(log n) Fibonacci via matrix exponentiation
- Implements fibonacci(n) using fast matrix exponentiation - Time complexity: O(log n), Space complexity: O(log n) - Full docstring with Wikipedia reference - Type hints on all parameters and return values - Doctests for edge cases: fib(0), fib(1), fib(10), fib(20), fib(50) - Raises ValueError for negative input
1 parent 791deb4 commit 53a30c4

File tree

1 file changed

+105
-0
lines changed

1 file changed

+105
-0
lines changed

maths/fibonacci_fast.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"""
2+
Fibonacci sequence via matrix exponentiation — O(log n) time complexity.
3+
4+
The standard recursive Fibonacci runs in O(2^n) time. Using matrix exponentiation
5+
we can compute the n-th Fibonacci number in O(log n) multiplications.
6+
7+
The key identity is:
8+
| F(n+1) F(n) | | 1 1 | ^ n
9+
| F(n) F(n-1) | = | 1 0 |
10+
11+
So F(n) = (M^n)[0][1] where M = [[1, 1], [1, 0]].
12+
13+
References:
14+
- https://en.wikipedia.org/wiki/Fibonacci_number#Matrix_form
15+
"""
16+
17+
18+
def _mat_mul(
19+
a: list[list[int]], b: list[list[int]]
20+
) -> list[list[int]]:
21+
"""Multiply two 2×2 integer matrices.
22+
23+
>>> _mat_mul([[1, 1], [1, 0]], [[1, 0], [0, 1]])
24+
[[1, 1], [1, 0]]
25+
"""
26+
return [
27+
[
28+
a[0][0] * b[0][0] + a[0][1] * b[1][0],
29+
a[0][0] * b[0][1] + a[0][1] * b[1][1],
30+
],
31+
[
32+
a[1][0] * b[0][0] + a[1][1] * b[1][0],
33+
a[1][0] * b[0][1] + a[1][1] * b[1][1],
34+
],
35+
]
36+
37+
38+
def _mat_pow(matrix: list[list[int]], power: int) -> list[list[int]]:
39+
"""Raise a 2×2 integer matrix to a non-negative integer power using
40+
fast exponentiation (repeated squaring).
41+
42+
:param matrix: A 2×2 matrix represented as a list of lists.
43+
:param power: Non-negative integer exponent.
44+
:return: matrix ** power
45+
46+
>>> _mat_pow([[1, 1], [1, 0]], 0)
47+
[[1, 0], [0, 1]]
48+
>>> _mat_pow([[1, 1], [1, 0]], 1)
49+
[[1, 1], [1, 0]]
50+
>>> _mat_pow([[1, 1], [1, 0]], 2)
51+
[[2, 1], [1, 1]]
52+
"""
53+
# Identity matrix
54+
result: list[list[int]] = [[1, 0], [0, 1]]
55+
while power:
56+
if power % 2 == 1:
57+
result = _mat_mul(result, matrix)
58+
matrix = _mat_mul(matrix, matrix)
59+
power //= 2
60+
return result
61+
62+
63+
def fibonacci(n: int) -> int:
64+
"""Return the n-th Fibonacci number using matrix exponentiation.
65+
66+
Time complexity: O(log n)
67+
Space complexity: O(log n) due to the call stack of _mat_pow
68+
69+
:param n: Non-negative integer index into the Fibonacci sequence
70+
(0-indexed: F(0)=0, F(1)=1, F(2)=1, ...).
71+
:raises ValueError: If *n* is negative.
72+
:return: The n-th Fibonacci number.
73+
74+
>>> fibonacci(0)
75+
0
76+
>>> fibonacci(1)
77+
1
78+
>>> fibonacci(2)
79+
1
80+
>>> fibonacci(10)
81+
55
82+
>>> fibonacci(20)
83+
6765
84+
>>> fibonacci(50)
85+
12586269025
86+
>>> fibonacci(-1)
87+
Traceback (most recent call last):
88+
...
89+
ValueError: fibonacci() only accepts non-negative integers
90+
"""
91+
if n < 0:
92+
raise ValueError("fibonacci() only accepts non-negative integers")
93+
if n == 0:
94+
return 0
95+
m: list[list[int]] = [[1, 1], [1, 0]]
96+
return _mat_pow(m, n)[0][1]
97+
98+
99+
if __name__ == "__main__":
100+
import doctest
101+
102+
doctest.testmod()
103+
104+
for i in range(15):
105+
print(f"fibonacci({i}) = {fibonacci(i)}")

0 commit comments

Comments
 (0)