Fibonacci in O(log n) šŸŽÆ

beginner
24 min

Fibonacci in O(log n) šŸŽÆ

Welcome to this exciting lesson on solving the Fibonacci sequence in O(log n) time complexity! In this tutorial, we'll explore the fascinating world of efficient algorithms, learn about the Fibonacci sequence, and implement a clever recursive approach that will impress both beginners and intermediate programmers alike.

Let's dive right in! šŸŠā€ā™‚ļø

Table of Contents

  1. Introduction to the Fibonacci Sequence

    • šŸ“ Definition
    • šŸ’” Real-world Applications
  2. Understanding Time Complexity

    • šŸ“ Big O Notation
    • šŸ’” Importance in Algorithm Analysis
  3. The Naive Approach to Solving Fibonacci (O(2^n))

    • šŸ“ Recursive Solution
    • šŸ’” Time Complexity Analysis
  4. The Exponential (O(n)) Approach to Solving Fibonacci

    • šŸ“ Bottom-Up Dynamic Programming Solution
    • šŸ’” Time Complexity Analysis
  5. The Logarithmic (O(log n)) Approach to Solving Fibonacci

    • šŸ“ Mathematical Insight
    • šŸ’” Recursive Solution with Optimization
  6. Implementing the Logarithmic Solution in Your Code

    • šŸ“ Python Code Example
    • šŸ’” Code Explanation and Optimizations
  7. Practice and Quiz

    • šŸ“ Coding Challenges
    • šŸ’” Solutions and Explanations

Introduction to the Fibonacci Sequence šŸ“

The Fibonacci sequence is a series of numbers in which each number is the sum of the two preceding ones:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...

The Fibonacci sequence has a rich history dating back to the 13th century, and it's found in a variety of areas, including mathematics, computer science, and art.

Understanding Time Complexity šŸ“

Before diving into our Fibonacci solutions, let's briefly discuss time complexity. Time complexity measures the efficiency of an algorithm by estimating the number of basic operations required to solve a problem as a function of the input size. We use Big O Notation to express this complexity.

The Naive Approach to Solving Fibonacci (O(2^n)) šŸ“

The simplest solution to the Fibonacci problem is a recursive one, but it comes with a steep time complexity of O(2^n). Let's analyze the recursive implementation below:

python
def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2)

šŸ’” Time Complexity Analysis: Since the number of recursive calls doubles with each additional level, the time complexity is O(2^n). In other words, the computation quickly becomes infeasible for large n.

The Exponential (O(n)) Approach to Solving Fibonacci šŸ“

To improve the time complexity, we can use a bottom-up dynamic programming approach. In this method, we compute and store the Fibonacci numbers as we go, reducing the time complexity to O(n).

python
def fibonacci(n): fib = [0, 1] for i in range(2, n + 1): fib.append(fib[i - 1] + fib[i - 2]) return fib[n]

šŸ’” Time Complexity Analysis: The for loop iterates n times, and each iteration requires constant time. Hence, the time complexity is O(n).

The Logarithmic (O(log n)) Approach to Solving Fibonacci šŸ“

The logarithmic approach to solving Fibonacci uses a mathematical insight that allows us to convert the problem into a sequence of exponentiations with a fixed base, thus reducing the time complexity to O(log n).

python
def log_fibonacci(n): if n <= 1: return n power = 1 fib = 1 num = n while num > 0: if num & 1: fib = fib * fibonacci(power) power *= 2 num >>= 1 return fib

šŸ’” Time Complexity Analysis: The while loop runs log_2(n) times, and each iteration requires a constant number of operations. Hence, the time complexity is O(log n).

šŸ“ Note: The & and >> operators represent bitwise AND and right shift, respectively.

Implementing the Logarithmic Solution in Your Code šŸ“

Now that we've learned about the logarithmic approach, let's implement it in Python:

python
def fibonacci(n): if n <= 1: return n power = 1 fib = 1 num = n while num > 0: if num & 1: fib = fib * fibonacci(power) power *= 2 num >>= 1 return fib print(fibonacci(10)) # Output: 55

šŸ’” Code Explanation and Optimizations:

  1. The function checks if n is less than or equal to 1, in which case it returns n directly.
  2. It initializes power and fib to 1, and num to n.
  3. The while loop iterates until num is zero, effectively converting the Fibonacci problem to a sequence of exponentiations.
  4. If the least significant bit (LSB) of num is set (i.e., num & 1 is true), it multiplies fib by the Fibonacci number at the current power.
  5. It doubles the power and shifts the num right by one bit in each iteration.

Practice and Quiz šŸ“

To solidify your understanding of the logarithmic approach to solving Fibonacci, try solving the following challenges:

  1. Challenge 1: Implement the logarithmic approach in your favorite programming language.
  2. Challenge 2: Analyze the time complexity of the following code:
python
def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2) + fibonacci(n - 3)

šŸ’” Solution: The time complexity is O(3^n), as the function calls itself three times in each recursive call.

:::quiz Question: What is the time complexity of the following function:

python
def fibonacci(n): if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2) + fibonacci(n - 3)

A: O(n) B: O(2^n) C: O(3^n) Correct: C Explanation: The function calls itself three times in each recursive call, resulting in a time complexity of O(3^n).