Skip to content

Fibonacci Sequence

LeetCode: 509. Fibonacci NumberDifficulty: Easy Pattern: Basic Recursion, Memoization


Problem Statement

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.

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

Given n, calculate F(n).


Examples

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

Example 4

Input: n = 10
Output: 55

Constraints

  • 0 <= n <= 30

Recursion Tree Visualization

Fibonacci Recursion Tree

Key Insight: Overlapping Subproblems

Notice how the same subproblems are computed multiple times:

  • fib(3) is computed 2 times
  • fib(2) is computed 3 times
  • fib(1) is computed 5 times

This exponential redundancy is why naive recursion is O(2^n)!

Call Trace for fib(5)

fib(5)
├── fib(4)
│   ├── fib(3)
│   │   ├── fib(2)
│   │   │   ├── fib(1) -> 1
│   │   │   └── fib(0) -> 0
│   │   └── fib(1) -> 1
│   └── fib(2)
│       ├── fib(1) -> 1
│       └── fib(0) -> 0
└── fib(3)
    ├── fib(2)
    │   ├── fib(1) -> 1
    │   └── fib(0) -> 0
    └── fib(1) -> 1

Total calls: 15
Result: 5

Recursive Template

python
def fib(n):
    """
    Fibonacci recursive template.

    Components:
    1. BASE CASES: F(0) = 0, F(1) = 1
    2. RECURSIVE CASE: F(n) = F(n-1) + F(n-2)
    """
    # BASE CASES
    if n <= 1:
        return n

    # RECURSIVE CASE (two recursive calls)
    return fib(n - 1) + fib(n - 2)

Solutions

Solution 1: Naive Recursion (O(2^n) - DON'T USE!)

python
def fib_naive(n: int) -> int:
    """
    Naive recursive Fibonacci - exponential time!

    Time Complexity: O(2^n) - each call branches into 2
    Space Complexity: O(n) - maximum call stack depth

    WARNING: Very slow for n > 30
    """
    if n <= 1:
        return n
    return fib_naive(n - 1) + fib_naive(n - 2)


# Test (slow for large n)
print(fib_naive(10))  # Output: 55

Complexity: Time O(2^n) · Space O(n)

  • Time: Each call branches into two recursive calls, creating a binary tree with 2^n nodes. Overlapping subproblems are recomputed, leading to exponential redundancy.
  • Space: Maximum recursion depth is n (the call stack grows linearly from fib(n) to fib(1)).

Solution 2: Memoization (Top-Down DP)

python
def fib_memo(n: int, memo: dict = None) -> int:
    """
    Fibonacci with memoization.

    Time Complexity: O(n) - each subproblem solved once
    Space Complexity: O(n) - memo dict + call stack

    This is the KEY optimization for recursion with overlapping subproblems!
    """
    if memo is None:
        memo = {}

    # Check if already computed
    if n in memo:
        return memo[n]

    # Base cases
    if n <= 1:
        return n

    # Compute and store result
    memo[n] = fib_memo(n - 1, memo) + fib_memo(n - 2, memo)
    return memo[n]


# Test
print(fib_memo(50))  # Output: 12586269025 (fast!)

Complexity: Time O(n) · Space O(n)

  • Time: Each subproblem from 0 to n is computed exactly once and stored in the memo dictionary, eliminating redundant calculations.
  • Space: The memo dictionary stores n entries, and the call stack can grow up to depth n.

Solution 3: Using @lru_cache (Pythonic)

python
from functools import lru_cache

@lru_cache(maxsize=None)
def fib_cached(n: int) -> int:
    """
    Fibonacci with Python's built-in memoization.

    Time Complexity: O(n)
    Space Complexity: O(n)

    Most Pythonic solution for interview!
    """
    if n <= 1:
        return n
    return fib_cached(n - 1) + fib_cached(n - 2)


# Test
print(fib_cached(50))  # Output: 12586269025

Complexity: Time O(n) · Space O(n)

  • Time: The @lru_cache decorator memoizes results, so each value from 0 to n is computed once.
  • Space: Cache stores n entries, plus O(n) call stack depth for the recursive calls.

Solution 4: Tabulation (Bottom-Up DP)

python
def fib_dp(n: int) -> int:
    """
    Fibonacci with bottom-up dynamic programming.

    Time Complexity: O(n)
    Space Complexity: O(n) - can be optimized to O(1)
    """
    if n <= 1:
        return n

    dp = [0] * (n + 1)
    dp[1] = 1

    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]


# Test
print(fib_dp(50))  # Output: 12586269025

Complexity: Time O(n) · Space O(n)

  • Time: Single pass from 2 to n, filling the dp array with one O(1) operation per iteration.
  • Space: The dp array stores n+1 values to build up the solution iteratively.

Solution 5: Space-Optimized (O(1) Space)

python
def fib_optimized(n: int) -> int:
    """
    Space-optimized Fibonacci - BEST SOLUTION.

    Time Complexity: O(n)
    Space Complexity: O(1) - only two variables!
    """
    if n <= 1:
        return n

    prev, curr = 0, 1
    for _ in range(2, n + 1):
        prev, curr = curr, prev + curr

    return curr


# Test
print(fib_optimized(50))  # Output: 12586269025
java
public int fib(int n) {
    if (n <= 1) return n;
    int prev = 0, curr = 1;
    for (int i = 2; i <= n; i++) {
        int next = prev + curr;
        prev = curr;
        curr = next;
    }
    return curr;
}

Complexity: Time O(n) · Space O(1)

  • Time: Single loop from 2 to n with constant-time operations per iteration.
  • Space: Only two variables (prev, curr) are maintained, regardless of input size.

Solution 6: Matrix Exponentiation (O(log n))

python
def fib_matrix(n: int) -> int:
    """
    Fibonacci using matrix exponentiation.

    Time Complexity: O(log n)
    Space Complexity: O(1)

    Based on: [[F(n+1), F(n)], [F(n), F(n-1)]] = [[1,1],[1,0]]^n
    """
    if n <= 1:
        return n

    def matrix_mult(A, B):
        """Multiply two 2x2 matrices."""
        return [
            [A[0][0]*B[0][0] + A[0][1]*B[1][0], A[0][0]*B[0][1] + A[0][1]*B[1][1]],
            [A[1][0]*B[0][0] + A[1][1]*B[1][0], A[1][0]*B[0][1] + A[1][1]*B[1][1]]
        ]

    def matrix_pow(M, p):
        """Matrix exponentiation using binary method."""
        result = [[1, 0], [0, 1]]  # Identity matrix
        while p:
            if p & 1:
                result = matrix_mult(result, M)
            M = matrix_mult(M, M)
            p >>= 1
        return result

    base = [[1, 1], [1, 0]]
    result = matrix_pow(base, n)
    return result[0][1]


# Test
print(fib_matrix(50))  # Output: 12586269025

Complexity: Time O(log n) · Space O(1)

  • Time: Matrix exponentiation uses binary method, halving the exponent each iteration, resulting in O(log n) matrix multiplications.
  • Space: Only 2x2 matrices are used (constant size), no recursion stack needed.

Complexity Analysis

ApproachTimeSpaceNotes
Naive RecursionO(2^n)O(n)DON'T USE - exponential
MemoizationO(n)O(n)Top-down DP
@lru_cacheO(n)O(n)Pythonic memoization
TabulationO(n)O(n)Bottom-up DP
Space-OptimizedO(n)O(1)Best for interviews
Matrix ExpoO(log n)O(1)Optimal but complex

Recursion Call Count Comparison

nNaive CallsMemoized Calls
5159
1017719
2021,89139
302,692,53759

Why Memoization Works

WITHOUT MEMOIZATION:              WITH MEMOIZATION:
       fib(5)                           fib(5)
      /      \                         /      \
   fib(4)   fib(3)                 fib(4)   fib(3) [cached]
   /    \   /    \                 /    \
fib(3) fib(2) ...               fib(3) fib(2) [cached]
                                /    \
                            fib(2) fib(1) [cached]

15 calls total                  9 calls total

Key Insight: Memoization transforms the recursion tree into a DAG (Directed Acyclic Graph), eliminating redundant computations.


Common Mistakes

1. Forgetting Memoization

python
# SLOW - O(2^n)
def fib_slow(n):
    if n <= 1:
        return n
    return fib_slow(n-1) + fib_slow(n-2)

# FAST - O(n) with @lru_cache
@lru_cache(maxsize=None)
def fib_fast(n):
    if n <= 1:
        return n
    return fib_fast(n-1) + fib_fast(n-2)

2. Mutable Default Argument

python
# DANGEROUS - memo persists across calls!
def fib_bad(n, memo={}):  # Don't do this!
    pass

# SAFE - initialize inside function
def fib_good(n, memo=None):
    if memo is None:
        memo = {}

Climbing Stairs (LeetCode 70)

Problem: You are climbing a staircase with n steps. Each time you can climb 1 or 2 steps. How many distinct ways can you climb to the top?

Key Insight: This is Fibonacci in disguise! ways(n) = ways(n-1) + ways(n-2).

Approach: Apply the same memoization/DP techniques as Fibonacci.

Complexity: O(n) time, O(1) space with optimization

Link: Local Solution

House Robber (LeetCode 198)

Problem: Given an array of non-negative integers representing money in houses, determine the maximum amount you can rob without robbing adjacent houses.

Key Insight: At each house, decide: rob it (add value + skip previous) or skip it (keep previous max).

Approach: DP with recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i])

Complexity: O(n) time, O(1) space

Link: LeetCode 198

N-th Tribonacci Number (LeetCode 1137)

Problem: The Tribonacci sequence: T(0)=0, T(1)=1, T(2)=1, and T(n) = T(n-1) + T(n-2) + T(n-3) for n >= 3. Return T(n).

Key Insight: Extension of Fibonacci - sum of three previous terms instead of two.

Approach: Track three variables instead of two. Same space optimization applies.

Complexity: O(n) time, O(1) space

Link: LeetCode 1137


Interview Tips

  1. Start with naive recursion to show understanding
  2. Identify overlapping subproblems - draw the tree
  3. Add memoization to optimize
  4. Discuss space optimization if asked
  5. Mention matrix exponentiation for bonus points

Key Takeaways

  1. Fibonacci is the classic example of overlapping subproblems
  2. Naive recursion is O(2^n) - never use in production
  3. Memoization reduces to O(n) - essential technique
  4. @lru_cache is the Pythonic way to memoize
  5. Space-optimized iterative is best for interviews
  6. Matrix exponentiation achieves O(log n) for advanced scenarios

Last updated: January 2026