Topics › Basics › Learn Basic Recursion
Fibonacci Number
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
The Fibonacci numbers are defined by F(0) = 0, F(1) = 1, and F(n) = F(n - 1) + F(n - 2) for n ≥ 2. Given n, return F(n).
This is the first recursion problem where the obvious recursive solution is a disaster: it makes two recursive calls per level and recomputes the same values an exponential number of times. Fixing that introduces memoisation, which is the gateway to dynamic programming, and the problem also has a neat O(log n) solution that is worth knowing.
Constraints: 0 ≤ n ≤ 30 on LeetCode (the answer fits in int). We return long so the methods are correct up to n = 92, the largest Fibonacci number that fits in 64 bits.
Example 1
n = 4
answer = 3 // F(4) = F(3) + F(2) = 2 + 1
Example 2
n = 10
answer = 55 // 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
Example 3
n = 0
answer = 0 // base case
Approach 1: Naive recursion
Intuition. Translate the definition directly. The call for n asks for F(n - 1) and F(n - 2) and adds the results. The base cases F(0) = 0 and F(1) = 1 collapse into if (n <= 1) return n.
Algorithm.
- If
n <= 1, returnn. - Otherwise return
naiveRecursion(n - 1) + naiveRecursion(n - 2).
static long naiveRecursion(int n) {
if (n <= 1) return n; // base cases F(0) = 0, F(1) = 1
return naiveRecursion(n - 1) + naiveRecursion(n - 2);
}
Why it is slow. Draw the call tree for n = 5: F(5) calls F(4) and F(3); F(4) calls F(3) and F(2); and so on. F(3) is computed twice, F(2) three times, F(1) five times. The number of calls grows like the Fibonacci numbers themselves, roughly 1.6^n. For n = 30 that is about 1.6 million calls (fine); for n = 50 it is about 20 billion (not fine).
Complexity. Time O(2^n) (more precisely O(φ^n) with φ ≈ 1.618). Space O(n) for the deepest chain of calls.
Approach 2: Memoised recursion
Intuition. The only problem with Approach 1 is repeated work: there are just n + 1 distinct sub-problems F(0) .. F(n), but the tree has exponentially many nodes. So remember each answer the first time it is computed and return the stored value on every later request. An array memo indexed by n, initialised to -1 (“unknown”), is enough. Every F(k) is now computed exactly once, and the tree collapses to a chain with cheap lookups hanging off it.
Algorithm.
- Allocate
memo[0..n]filled with-1. fibMemo(n): ifn <= 1, returnn. Ifmemo[n] != -1, return it.- Otherwise compute
fibMemo(n - 1) + fibMemo(n - 2), store it inmemo[n], and return it.
static long memoised(int n) {
long[] memo = new long[Math.max(n + 1, 2)];
Arrays.fill(memo, -1); // -1 marks "not computed yet"
return fibMemo(n, memo);
}
private static long fibMemo(int n, long[] memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
return memo[n];
}
Complexity. Time O(n): each of the n + 1 values is computed once, and every other call is an O(1) lookup. Space O(n) for the memo array plus O(n) for the recursion stack.
Approach 3: Iterative with two variables
Intuition. Memoisation fills the array in the order F(2), F(3), ..., F(n) anyway, so compute it bottom-up with a loop instead. Then notice that F(i) only ever needs the two previous values, so the whole array can shrink to two variables, prev and cur. Each iteration shifts the window one step to the right.
Algorithm.
- If
n <= 1, returnn. - Set
prev = 0(F(0)),cur = 1(F(1)). - For
ifrom2ton:next = prev + cur,prev = cur,cur = next. - Return
cur.
static long iterative(int n) {
if (n <= 1) return n;
long prev = 0, cur = 1; // F(0), F(1)
for (int i = 2; i <= n; i++) {
long next = prev + cur;
prev = cur;
cur = next;
}
return cur;
}
Dry run on n = 5:
| i | prev | cur | next |
|---|---|---|---|
| start | 0 | 1 | |
| 2 | 1 | 1 | 1 |
| 3 | 1 | 2 | 2 |
| 4 | 2 | 3 | 3 |
| 5 | 3 | 5 | 5 |
Return cur = 5, and indeed F(5) = 5.
Complexity. Time O(n), space O(1). This is the answer to give in an interview unless n is enormous.
Approach 4: Fast doubling
Intuition. Two identities let us jump from F(k) and F(k + 1) straight to F(2k) and F(2k + 1):
F(2k) = F(k) * (2 * F(k + 1) - F(k))
F(2k + 1) = F(k)^2 + F(k + 1)^2
So compute the pair for n / 2 recursively, then derive the pair for n with a handful of multiplications. Halving n at every step gives O(log n) calls. Returning the pair {F(n), F(n + 1)} from each call is the trick that makes the identities usable, because both are needed for the next doubling.
Algorithm.
fibPair(0)returns{0, 1}.- Otherwise get
{a, b} = fibPair(n / 2), i.e.a = F(k),b = F(k + 1)withk = n / 2. - Compute
c = a * (2b - a)(this isF(2k)) andd = a^2 + b^2(this isF(2k + 1)). - If
nis even, return{c, d}; otherwise return{d, c + d}.
static long fastDoubling(int n) {
return fibPair(n)[0];
}
/** Returns {F(n), F(n+1)}. */
private static long[] fibPair(int n) {
if (n == 0) return new long[]{0, 1};
long[] half = fibPair(n / 2);
long a = half[0], b = half[1]; // a = F(k), b = F(k+1) with k = n / 2
long c = a * (2 * b - a); // F(2k)
long d = a * a + b * b; // F(2k+1)
if (n % 2 == 0) return new long[]{c, d}; // n = 2k: {F(2k), F(2k+1)}
return new long[]{d, c + d}; // n = 2k + 1: {F(2k+1), F(2k+2)}
}
Check it on n = 3: fibPair(1) uses fibPair(0) = {0, 1} to get c = 0, d = 1, and since 1 is odd returns {1, 1}. Then fibPair(3) has a = 1, b = 1, so c = 1 * (2 - 1) = 1 and d = 1 + 1 = 2; 3 is odd, so it returns {2, 3}, and F(3) = 2 is correct.
Complexity. Time O(log n), space O(log n) for the recursion stack. It only pays off when n is huge and the arithmetic is done modulo a prime or with big integers; for n ≤ 92 the iterative version is just as good.
Edge cases and pitfalls
n = 0andn = 1. Both are base cases and must be handled before touching any array or loop.if (n <= 1) return ncovers both.- Overflow.
F(47) = 2,971,215,073already exceedsint.F(92)fits inlong,F(93)does not. Beyond that useBigIntegeror compute modulo10^9 + 7as many judges ask. In fast doubling, the intermediated = a^2 + b^2forn = 92isF(93)and overflows even thoughF(92)itself fits, so that method is safe only ton = 91; the tests stop at90. - Exponential blow-up. Never submit Approach 1 for
nbeyond about 35. Its only purpose is to make the repeated sub-problems visible. - Memo sentinel.
-1is a safe “unknown” marker because Fibonacci numbers are never negative. If a memoised function could legitimately return-1, use a separateboolean[]or aLong[]withnull. - Recursion depth of memoisation. The memoised version still recurses
nlevels deep, so fornin the hundreds of thousands prefer the iterative version.
Related problems
- Climbing stairs is the Fibonacci recurrence in disguise and the standard first dynamic-programming problem.
- Factorial of a given number and Sum of First N Numbers are single-call recursions where the naive version is already linear.
- Pow(x, n) uses the same halving idea as fast doubling to compute a power in
O(log n). - Print 1 to N using Recursion is where the recursion basics used here are introduced.