Problem overview
The Fibonacci sequence starts with 0 and 1, and every later number is the sum of the two before it: F(0) = 0, F(1) = 1 and F(n) = F(n - 1) + F(n - 2) for n > 1. The method fibonacci(int n) returns F(n).
The sequence begins 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55. So fibonacci(2) is 1 (1 + 0), fibonacci(3) is 2 (1 + 1) and fibonacci(4) is 3 (2 + 1). The lesson asks for O(n) time and O(1) extra space.
Approach: keep only the last two numbers
The definition translates directly into a recursive method:
static int fibRecursive(int n) {
return n < 2 ? n : fibRecursive(n - 1) + fibRecursive(n - 2);
}
It is correct, but slow. fibRecursive(n) calls itself twice, and those calls repeat each other's work: F(n - 2) is computed once inside F(n - 1) and again directly. The number of calls grows exponentially, roughly 1.6 times for every step up in n, so the time is O(2^n) in the usual loose bound, and the recursion depth makes the stack use O(n). Somewhere in the 40s it already takes noticeable time, and every extra step makes it about 1.6 times slower.
The loop version relies on one observation: to compute the next number, only the previous two are needed. Everything older can be forgotten. Two variables, prev and curr, hold the last two values, and each iteration slides that window one step forward. That computes every number exactly once, so the time is O(n), and two variables are all the memory it needs, so the space is O(1).
Step by step
- Handle the base cases. If
n < 2, the answer isnitself:F(0) = 0andF(1) = 1. One check covers both. - Start the window.
prev = 0isF(0)andcurr = 1isF(1). Java allowsint prev = 0, curr = 1;on one line. - Loop from 2 to
ninclusive. These are the numbers still to compute,F(2)throughF(n). At the start of the pass for indexi,prevholdsF(i - 2)andcurrholdsF(i - 1). - Compute
next. It iscurr + prev, which isF(i). - Slide the window. Copy
currintoprevfirst, thennextintocurr. The order matters: reversed, the oldcurrwould be overwritten before it could be saved, and both variables would end up holding the new value. - Return
curr. After the last pass it holdsF(n).
The solution in Java
class Solution {
public int fibonacci(int n) {
if (n < 2) {
return n;
}
int prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
int next = curr + prev;
prev = curr;
curr = next;
}
return curr;
}
}
Tracing fibonacci(5): the window starts as prev = 0, curr = 1. The pass for i = 2 gives next = 1, so the window becomes (1, 1). Then i = 3 gives 2 and the window (1, 2), i = 4 gives 3 and (2, 3), and i = 5 gives 5 and (3, 5). The method returns curr, which is 5.
The variable next is declared inside the loop, so a fresh one exists on every pass and disappears at the end of it. Only prev and curr carry information from one pass to the next.
For inputs beyond 46, the same method with long variables works, and only the types change:
static long fibonacciLong(int n) {
if (n < 2) {
return n;
}
long prev = 0, curr = 1;
for (int i = 2; i <= n; i++) {
long next = curr + prev;
prev = curr;
curr = next;
}
return curr;
}
With it, fibonacciLong(47) is 2971215073 and fibonacciLong(92) is 7540113804746346429, the largest Fibonacci number that fits in a long.
Complexity
The loop body runs n - 1 times for n >= 2, with a fixed amount of work each, so the time is O(n). The method creates prev, curr, next and i and nothing that grows with n, so the extra space is O(1). A table of all the values (an array of size n + 1) would give the same time but O(n) space, which is why the sliding window is preferred.
Edge cases and common mistakes
- Integer overflow. An
intholds values up to 2,147,483,647 and wraps around silently beyond that.fibonacci(46)is 1836311903, the largest Fibonacci number that fits.fibonacci(47)returns -1323752223, because the true value 2971215073 does not fit. Java gives no error for this. If larger inputs are needed, uselongfor the three variables, which stays correct up toF(92). - Missing the base cases. Without the
n < 2check,n = 0would return 1, since the loop never runs andcurrstill holds its starting value. - Updating in the wrong order. Writing
curr = next; prev = curr;sets both variables to the new number and breaks every later value. - Looping to
n - 1or from 1. A loop that stops one early returnsF(n - 1); one that starts at 1 does one pass too many. - Using the naive recursion. It passes small tests and then times out, because of the repeated work described above.
Testing
Useful inputs: 0 returns 0, 1 returns 1, 2 returns 1, 10 returns 55 and 46 returns 1836311903. The first two check the base case, the third is the first pass of the loop, and the last sits at the edge of what an int can hold.
