Fibonacci

Subpage of Algorithms

Recurrences, asymptotics, and design techniques.

There are multiple paradigms to compute the nth Fibonacci number, ranging from exponential-time recursion to logarithmic-time matrix exponentiation and even closed-form formulas.

The different approaches reveal different approaches used across the board in the study of algorithms.

Each approach highlights different trade-offs in time and space complexity, and analyzing them requires careful reasoning about recurrence relations, dynamic programming states, and asymptotic growth.

MethodIdeaTime ComplexitySpace ComplexityAnalysis Notes
Naïve RecursionDirectly use definition \(F(n) = F(n-1) + F(n-2)\).\(O(2^n)\)\(O(n)\) (stack depth)Exponential blow-up due to repeated recomputation of subproblems. Complexity derived from recurrence \(T(n) = T(n-1) + T(n-2) + O(1)\).
Dynamic Programming (Tabulation)Iteratively build array from base cases up to \(n\).\(O(n)\)\(O(n)\)Each state computed once, linear scan. Complexity comes from summing constant work across (n) steps.
Dynamic Programming (Space-Optimized)Only store last two values.\(O(n)\)\(O(1)\)Same recurrence, but memory reduced by discarding unused states.
Matrix ExponentiationUse identity: \(\begin{bmatrix}1 & 1 \ 1 & 0\end{bmatrix}^n = \begin{bmatrix}F_{n+1} & F_n \ F_n & F_{n-1}\end{bmatrix}\).\(O(\log n)\)\(O(1)\)Fast exponentiation via repeated squaring. Complexity from halving exponent each step.
Fast Doubling MethodUse formulas: \(F(2k) = F(k)[2F(k+1) - F(k)])\), \((F(2k+1) = F(k+1)^2 + F(k)^2\).\(O(\log n)\)\(O(1)\)Similar to matrix exponentiation but avoids explicit matrices.
Closed-Form (Binet’s Formula)\(F(n) = \frac{\varphi^n - \psi^n}{\sqrt{5}}\), where \(\varphi = \frac{1+\sqrt{5}}{2}\), \(\psi = \frac{1-\sqrt{5}}{2}\).\(O(1)\)to \(O(logn)\)depending on arithmetic\(O(1)\)Requires floating-point exponentiation; precision errors grow with (n). Complexity analysis assumes constant-time arithmetic, but in practice large (n) requires arbitrary precision.

Closed-Form Analysis

Binet’s formula is constant-time in theory, but complexity depends on the cost of computing \(\varphi^n\). With floating-point arithmetic, exponentiation is not truly constant for large \(n\). For exact integer results, arbitrary-precision arithmetic introduces hidden costs.

Fast Exponentiation

This comes up a lot in Fibonacci and is a useful general trick.

Fast exponentiation, also known as exponentiation by squaring, is an efficient algorithm to compute \(a^n\) in logarithmic time, rather than the naive linear time. It works by reducing the number of multiplications needed by breaking down the exponent into binary components (powers of two).

Here is a great explanation of how the algorithm works: https://alexanderobregon.substack.com/p/fast-exponentiation-logic-in-java

In pseudo-code form, this is:

int expo(int a, int b) {
  int result = 1;

  while (b) {
    if (b%2==1) {
      result *= a;
    }
    b /= 2;
    a *= a;
  }

  return result;
}