惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Engineering at Meta
Engineering at Meta
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
博客园 - 聂微东
酷 壳 – CoolShell
酷 壳 – CoolShell
月光博客
月光博客
Last Week in AI
Last Week in AI
博客园_首页
I
InfoQ
T
Tailwind CSS Blog
爱范儿
爱范儿
雷峰网
雷峰网
Recent Announcements
Recent Announcements
F
Fortinet All Blogs
B
Blog
WordPress大学
WordPress大学
A
About on SuperTechFans
V
Visual Studio Blog
有赞技术团队
有赞技术团队
P
Proofpoint News Feed

Speculative Branches

Returning to Perfect Randomness Exponentials in 3 Instructions Perfect Random Floating-Point Numbers A Cryptographically Secret Santa Time Programming for Lawyers and Jurors Five Nine Problems The Computer Architecture of AI (in 2024) The Knight Capital Disaster Abstraction is Expensive Contemplating Randomness Introduction to Micro-Optimization Rest in Peace, Optane Use One Big Server The Most Useful Statistical Test You Didn't Learn in School What Happened with FPGA Acceleration? Teach Your Kids Bridge Fixed Point Arithmetic You (Probably) Shouldn't use a Lookup Table Who Controls a DAO? Python is Like Assembly Racing the Hardware: 8-bit Division The Meaning of Speed Performance Numbers Worth Knowing Constant-time Fibonacci First Post
Less-than-linear Fibonacci
2022-01-14 · via Speculative Branches

Few interview problems are as notorious as the "Fibonacci" interview question. At first glance, it seems good: Most people know something about the problem, and there are several clever ways to achieve a linear time solution. Usually, in interviews, the linear time solution is the expected solution. However, the Fibonacci problem is unique among interview problems in that the expected solution is not the optimal solution. There is an $O(1)$ solution, and to get there, we need a little bit of linear algebra.

In this part, we are going to use basic linear algebra to get to $O(\log(n))$ time complexity. In the next part, we build on this with some more advanced math to get to a generic $O(1)$ solution for Fibonacci-like problems.

The Fibonacci Interview Problem

The Fibonacci numbers are the sequence of numbers constructed such that each number is the sum of the two previous numbers in the sequence, starting with 0 and 1: $$ F_0 = 0 \\ F_1 = 1 \\ F_n = F_{n-1} + F_{n-2} $$ The resulting sequence is: $$ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ... $$ And so on. You can generalize the formula by changing the initial conditions or the recursive formula (which mathematicians call a recurrence relation). Everything we are discussing applies to the generalized problem.

The interview problem is simple: given a number n, compute the nth Fibonacci number.

According to tradition, the optimal solution to the Fibonacci interview problem is to compute the entire sequence up to that point, using either recursion with memoization or one-dimensional dynamic programming. Both result in a solution that uses linear time and space.

Recurrence Relations and Systems of Equations

To compute the nth Fibonacci in constant time, we need a closed-form solution. There are Googleable solutions for the Fibonacci recurrence relation (specifically Binet's Formula), but we would like to create a general solution for this class of problem. It turns out that there is a general method to derive a closed-form solution for any recurrence relation, and it is better to learn it once than to look for a Fibonacci-specific answer.

We start by creating a system of equations. The recurrence relation is one of those equations, and the other equations are simple equalities. For the Fibonacci sequence, our system is: $$ F_{n + 1} = F_{n} + F_{n - 1} \\ F_n = F_n $$ We can represent the system as a matrix equation: $$ \begin{bmatrix} F_{n+1} \\ F_{n} \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix} F_{n} \\ F_{n-1} \end{bmatrix} $$ This is a restatement of the equation above. The top row corresponds to $F_{n + 1} = F_{n} + F_{n - 1}$ and the bottom row is $F_n = F_n$. The left side is the result of the equations, and the right side is the product of the coefficient matrix and the variables that feed into the equations.

Let's call the big matrix $\textbf{A}$: $$ \textbf{A} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} $$ If the recurrence relation changes, $\textbf{A}$ changes. Using this form, if we want $F_2$, the formula is: $$ \begin{bmatrix} F_{2} \\ F_{1} \end{bmatrix} = \textbf{A} \begin{bmatrix} F_{1} = 1 \\ F_{0} = 0 \end{bmatrix} $$ If we want the third Fibonacci number, we need to do a little more work: $$ \begin{bmatrix} F_{3} \\ F_{2} \end{bmatrix} = \textbf{A} \begin{bmatrix} F_{2} \\ F_{1} \end{bmatrix} = \textbf{A}^2 \begin{bmatrix} 1 \\ 0 \end{bmatrix} $$ We have an interesting relationship here: By computing powers of $\textbf{A}$, we can directly calculate Fibonacci numbers. Our task is now to compute the power of the $\textbf{A}$ matrix quickly. Even the simplest algorithm for this, multiplying $\textbf{A}$ by itself $n$ times, takes $O(n)$ time and $O(1)$ space. This is already better than the "optimal" solution that you can find in Cracking the Coding Interview.

Generalizing Beyond Fibonacci

If we have a different recurrence relation, we can still do this trick. For example, with $X_{n+1} = 2 X_n + 3 X_{n-1}$, $ \textbf{A} $ changes to:

$$ \textbf{A}_X = \begin{bmatrix} 2 & 3 \\ 1 & 0 \end{bmatrix} $$

And for $Y_{n+1} = Y_n + 2 Y_{n-1} + 3 Y_{n-2}$, we can add another equality to the system of equations:

$$ \textbf{A}' = \begin{bmatrix} 1 & 2 & 3 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{bmatrix} $$

The full equation is:

$$ \begin{bmatrix} F_{n+1} \\ F_{n} \\ F_{n-1} \end{bmatrix} = \textbf{A}' \begin{bmatrix} F_{n} \\ F_{n-1} \\ F_{n-2} \end{bmatrix} $$

Getting Logarithmic

We have reduced the problem to efficiently computing $\textbf{A}^n$ to get the nth Fibonacci number. Matrix multiplication is associative, so we can re-order the computation of the matrix products for efficiency. A convenient way to do this is to compute the powers of two of the matrix by repeated squaring, and then decomposing the exponentiation into a product of powers of two. For example:

$$ \textbf{A}^{53} = \textbf{A}^{32} * \textbf{A}^{16} * \textbf{A}^4 * \textbf{A}^1 $$

This is similar to how binary code decomposes numbers into a set of ones, so we can use the bits of $n$ to decide which powers to multiply to make $A^n$: When you have a one bit, multiply the corresponding power of $\textbf{A}$ into the result. When you have a zero bit, don't, and stop when you reach the most significant one bit in $n$.

Note that we do either one or two matrix multiplications (constant time) per bit until we reach the most significant bit of $n$, and then we are done. Starting from the first bit in the ones position, the MSB of $n$ is bit number $\log_2(n)$, so we have $O(\log_2(n))$ time complexity (with constant space).

Working in Python, we get something like the following code. Rust, Python, and many other languages have well-supported easy-to-use matrix multiplication routines, so we are using Python. BLAS is also available for languages like C, C++, and Fortran.

 1import numpy as np
 2
 3def log_fibonacci(n):
 4    # Initialize the A matrix and the matrix that represents A^n
 5    a = np.array([[1, 1],
 6                  [1, 0]])
 7    result = np.identity(2)
 8
 9    # Computing A^n gives us Fibonacci number n + 1, so decrement n
10    n -= 1
11    while n != 0:
12        # Multiply by the A matrix power if we have a one bit
13        if n & 1 == 1:
14            result = result @ a
15        
16        # Square the a matrix and move to the next bit
17        a = a @ a
18        n >>= 1
19
20    # Apply the initial conditions
21    initial_conditions = np.array([1, 0])
22    return (result @ initial_conditions)[0]

Conclusions

By pairing a little bit of traditional mathematics with algorithms, we have a solution that is a lot better than the traditional "algorithms" approach to the problem of computing Fibonacci numbers. In the next part, we are going to apply more math to do even better.

Go on to part II.