Recursive Fibonacci in Python with Time Complexity Analysis


The Fibonacci numbers, commonly denoted F(n) form a sequence, called the Fibonacci sequence, such that each number is the sum of the two preceding ones, starting from 0 and 1. That is,

F(0) = 0, F(1) = 1
F(n) = F(n - 1) + F(n - 2), for n > 1.

Write a recursive function that given n, returns F(n).

Example 1:

Input: n = 2
Output: 1
Explanation: F(2) = F(1) + F(0) = 1 + 0 = 1.

Example 2:

Input: n = 3
Output: 2
Explanation: F(3) = F(2) + F(1) = 1 + 1 = 2.

Example 3:

Input: n = 4
Output: 3
Explanation: F(4) = F(3) + F(2) = 2 + 1 = 3.

Constraints:
  • 0 <= n <= 30

Understanding the Problem

The core challenge of this problem is to compute the nth Fibonacci number using a recursive approach. The Fibonacci sequence is a classic example in computer science and mathematics, often used to teach recursion and dynamic programming.

Common applications of the Fibonacci sequence include algorithm analysis, financial models, and biological settings like population growth models.

Potential pitfalls include misunderstanding the base cases and not handling the recursive calls correctly, which can lead to infinite recursion or incorrect results.

Approach

To solve this problem, we can start with a naive recursive solution and then discuss its limitations and potential optimizations.

Naive Recursive Solution

The naive approach directly translates the mathematical definition of the Fibonacci sequence into a recursive function. However, this approach is not optimal due to its exponential time complexity.

def fibonacci(n):
    # Base case: if n is less than 2, return n
    if n < 2:
        return n
    # Recursive case: return the sum of the two preceding numbers
    return fibonacci(n - 1) + fibonacci(n - 2)

While this solution is simple and elegant, it has a time complexity of O(2^n), which makes it impractical for large values of n.

Optimized Solutions

To improve the efficiency, we can use memoization or an iterative approach.

Memoization

Memoization stores the results of expensive function calls and reuses them when the same inputs occur again, reducing the time complexity to O(n).

def fibonacci_memo(n, memo={}):
    # Base case: if n is less than 2, return n
    if n < 2:
        return n
    # Check if the result is already in the memo dictionary
    if n not in memo:
        # Store the result in the memo dictionary
        memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
    return memo[n]

Iterative Approach

The iterative approach uses a loop to compute the Fibonacci numbers, which also has a time complexity of O(n) and a space complexity of O(1).

def fibonacci_iterative(n):
    if n < 2:
        return n
    a, b = 0, 1
    for _ in range(2, n + 1):
        a, b = b, a + b
    return b

Algorithm

Let's break down the memoization algorithm step-by-step:

  1. Check the base case: if n is less than 2, return n.
  2. Check if the result for n is already in the memo dictionary.
  3. If not, compute the result recursively and store it in the memo dictionary.
  4. Return the result from the memo dictionary.

Code Implementation

Here is the Python code for the memoization approach:

def fibonacci_memo(n, memo={}):
    # Base case: if n is less than 2, return n
    if n < 2:
        return n
    # Check if the result is already in the memo dictionary
    if n not in memo:
        # Store the result in the memo dictionary
        memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo)
    return memo[n]

# Example usage
print(fibonacci_memo(2))  # Output: 1
print(fibonacci_memo(3))  # Output: 2
print(fibonacci_memo(4))  # Output: 3

Complexity Analysis

The naive recursive solution has a time complexity of O(2^n) and a space complexity of O(n) due to the call stack.

The memoization approach reduces the time complexity to O(n) and the space complexity to O(n) due to the memo dictionary.

The iterative approach also has a time complexity of O(n) but a space complexity of O(1).

Edge Cases

Potential edge cases include:

Testing

To test the solution comprehensively, include a variety of test cases:

# Test cases
print(fibonacci_memo(0))  # Output: 0
print(fibonacci_memo(1))  # Output: 1
print(fibonacci_memo(10))  # Output: 55
print(fibonacci_memo(15))  # Output: 610
print(fibonacci_memo(30))  # Output: 832040

Thinking and Problem-Solving Tips

When approaching such problems:

Conclusion

In this blog post, we discussed the problem of computing the nth Fibonacci number using a recursive approach. We explored the naive recursive solution, its limitations, and optimized solutions using memoization and iteration. Understanding and solving such problems is crucial for developing strong problem-solving skills in computer science.

We encourage readers to practice and explore further to deepen their understanding.

Additional Resources