Problem overview
The method fibonacci(n: Int) returns F(n), where F(0) = 0, F(1) = 1, and every later number is the sum of the two before it: F(n) = F(n - 1) + F(n - 2). The sequence begins 0, 1, 1, 2, 3, 5, 8, 13.
For example, F(2) = 1 + 0 = 1, F(3) = 1 + 1 = 2 and F(4) = 2 + 1 = 3. The lesson asks for O(n) time and O(1) extra space.
Approach
Translating the definition straight into a recursive method is correct but slow: each call makes two more calls, and the same values are computed over and over, so the time grows exponentially. Storing all earlier values in an array fixes the time but costs O(n) space.
The lesson's version notices that computing the next number only needs the last two. Keep those two in variables, prev and curr, and move them forward one position per pass. After the loop, curr holds F(n). That does n - 1 additions and uses a fixed number of variables.
How much work does the recursive version waste? Counting its calls gives 177 for F(10), 21,891 for F(20) and 2,692,537 for F(30). Ten more steps multiply the work by about a hundred. The loop needs 9, 19 and 29 passes for the same three inputs.
Why do two variables suffice? F(i) depends only on F(i - 1) and F(i - 2). Once F(i) is known, F(i - 2) is never needed again, so its slot can be reused. This idea of keeping only the part of the past that the next step needs comes back in many problems.
Step by step
- Handle the base cases. If
n < 2, returnn. This covers bothF(0) = 0andF(1) = 1with one check. - Set up the window.
var prev = 0andvar curr = 1areF(0)andF(1). They arevars because they change on every pass. - Loop from 2 to
ninclusive.for (i <- 2 to n)runs once for each ofF(2)throughF(n). At the start of passi,prevholdsF(i - 2)andcurrholdsF(i - 1). - Compute the new number.
val next = curr + previsF(i). It is avalbecause it is set once per pass. - Slide the window. Assign
prev = currfirst, thencurr = next. The order matters: doing it the other way round overwritescurrbefore its old value can be copied intoprev. - Return
curr. After the last pass it holdsF(n).
Here is the loop for n = 5:
i | prev at start | curr at start | next | curr at end |
|---|---|---|---|---|
| 2 | 0 | 1 | 1 | 1 |
| 3 | 1 | 1 | 2 | 2 |
| 4 | 1 | 2 | 3 | 3 |
| 5 | 2 | 3 | 5 | 5 |
The method returns 5, which is F(5).
The solution in Scala
class Solution {
def fibonacci(n: Int): Int = {
if (n < 2) {
return n
}
var prev = 0
var curr = 1
for (i <- 2 to n) {
val next = curr + prev
prev = curr
curr = next
}
return curr
}
}
Some Scala details:
- Inclusive range.
2 to nincludesn. Withuntilthe loop would stop one pass early and returnF(n - 1). - The loop variable is only a counter. The body never reads
i; the loop just repeats the updaten - 1times. Writingfor (_ <- 2 to n)says so explicitly and behaves the same. returnin the middle. The earlyreturn nis in anif, not inside a lambda, so it is an ordinary return. For the final line,curralone would also work, because the last expression of a block is its value.- 32-bit
Int. The result wraps silently once it passes 2,147,483,647.
The same idea without var
Scala programmers often express this kind of loop with foldLeft, which carries the pair of numbers from one pass to the next instead of mutating variables:
def fib(n: Int): Int =
if (n < 2) n
else (2 to n).foldLeft((0, 1)) { case ((prev, curr), _) => (curr, prev + curr) }._2
The pair starts as (0, 1), each step replaces (prev, curr) with (curr, prev + curr), and ._2 picks the second item of the final pair. The underscore in the pattern ignores the range value, which is only a counter. It does the same n - 1 steps and returns the same values: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 for n from 0 to 10. The lesson uses the explicit loop because it shows the sliding window step by step.
Complexity
Time: O(n). The loop runs n - 1 times and each pass does one addition and two assignments.
Space: O(1). The method stores prev, curr, next and the loop counter, no matter how big n is.
Edge cases and common mistakes
n = 0andn = 1. They return 0 and 1 from the base case. Without it, the loop2 to nis empty and the method would return 1 forn = 0, which is wrong.- Overflow.
F(46)is 1836311903, the largest Fibonacci number that fits in anInt.fibonacci(47)gives -1323752223 because the true value, 2971215073, wraps around. If larger inputs matter, useLongfor the variables and return type. - Updating in the wrong order. Writing
curr = nextbeforeprev = currsetsprevto the new number and corrupts every later value. - Off-by-one in the range. Starting the loop at 1, or ending at
n - 1, givesF(n + 1)orF(n - 1). - Plain recursion. The two-call recursive version repeats work. Computing
F(30)that way makes 2,692,537 calls, against 29 passes of the loop.
Testing
n = 0gives 0 andn = 1gives 1.n = 2gives 1,n = 3gives 2,n = 4gives 3.n = 10gives 55.n = 46gives 1836311903.
