Problem overview
The Fibonacci sequence starts with F(0) = 0 and F(1) = 1, and every later number is the sum of the two before it: F(n) = F(n - 1) + F(n - 2) for n > 1. The method fibonacci takes an Int named n, between 0 and 30, and returns F(n) using recursion.
The first terms are 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55. So fibonacci(2) is 1, fibonacci(3) is 2, and fibonacci(4) is 3.
The statement says not to worry about time and space, so the goal is a clear translation of the definition into code.
Approach: the definition is the algorithm
The formula already describes a recursive method. It has two base cases, F(0) and F(1), which can be answered immediately, and a recursive case that combines the answers for n - 1 and n - 2. Both arguments are smaller than n, so every chain of calls moves toward a base case and ends.
The two base cases have a convenient shared property: for n equal to 0 or 1, the answer is n itself. One check, n < 2, covers both.
Step by step
- If
n < 2, returnn. This handlesF(0) = 0andF(1) = 1. Without it, each call would make two more calls forever, and the program would crash. - Otherwise, return
fibonacci(n - 1) + fibonacci(n - 2).
For fibonacci(4), the calls expand like this:
fibonacci(4)
fibonacci(3)
fibonacci(2)
fibonacci(1) = 1
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(2)
fibonacci(1) = 1
fibonacci(0) = 0
Adding from the bottom: fibonacci(2) is 1 + 0 = 1, fibonacci(3) is 1 + 1 = 2, and fibonacci(4) is 2 + 1 = 3.
The solution in Scala
class Solution {
def fibonacci(n: Int): Int = {
if (n < 2) {
return n
}
return fibonacci(n - 1) + fibonacci(n - 2)
}
}
Scala details
A method that calls itself must declare its result type, which is why the signature says : Int. The compiler won't infer it for a recursive method.
The last expression of a method is its value, so return can be left out. The same method can be written as a single expression:
def fibonacci(n: Int): Int =
if (n < 2) n else fibonacci(n - 1) + fibonacci(n - 2)
Here if / else is an expression that produces a value. The lesson's version spells out return, and the two behave the same.
An Int is 32 bits and overflow wraps silently. Fibonacci numbers fit comfortably for the allowed inputs: fibonacci(30) is 832040. The first number that does not fit in an Int is F(47), but a recursive calculation of that size would take far too long anyway.
Complexity
Each call that isn't a base case makes two more calls, so the number of calls grows exponentially: roughly like 1.6 to the power n. Counting the calls directly gives 15 for fibonacci(5) and 2,692,537 for fibonacci(30). That is fine for n up to 30, and it explains the limit in the constraints. The time is exponential, and the stack holds at most n calls at a time, so the space is O(n).
The slowness comes from repeated work. In the expansion of fibonacci(4) above, fibonacci(2) is computed twice and fibonacci(1) three times. Storing results that were already computed, or building the sequence from the bottom up with a loop, brings the time down to O(n). This lesson shows the plain version so that you see the recursion first.
Edge cases and common mistakes
- Forgetting the base case. The calls never stop, and the program ends with a
StackOverflowError. - Handling only
n == 0. Thenfibonacci(1)callsfibonacci(0)andfibonacci(-1), and the second one never reaches a base case. Both 0 and 1 must be base cases. - Using the wrong threshold. With
n <= 2returningn,fibonacci(2)would return 2 instead of 1. - Returning the sum of the calls on
n. The recursive calls must usen - 1andn - 2. Calling withnagain repeats the same problem and never ends. - Negative input. The constraints keep
nat 0 or above. Since-1 < 2, a negativenwould simply returnnitself, which is not a Fibonacci number.
Testing
fibonacci(0)returns 0 andfibonacci(1)returns 1.fibonacci(2)returns 1.fibonacci(10)returns 55.fibonacci(30)returns 832040.
