Problem overview
The method fibonacci(_ n: Int) returns F(n), the n-th Fibonacci number. The 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). The sequence begins 0, 1, 1, 2, 3, 5, 8, 13.
For n = 4, F(4) = F(3) + F(2) = 2 + 1 = 3. The statement asks for O(n) time and O(1) space.
Approach
The definition suggests recursion: F(n) calls F(n - 1) and F(n - 2). That works, but it recomputes the same values again and again, and its running time grows exponentially. A loop avoids this. To get the next number you only need the last two, so keep just those two values, produce the next one, and slide forward by one position. After n - 1 steps beyond the starting pair, you hold F(n). Two variables are all the memory required, which is the O(1) space the statement asks for.
Step by step
- Handle the base cases first. If
n < 2, returnn. SinceF(0) = 0andF(1) = 1, the answer equals the input, and one check covers both. - Declare the two running values in one
vardeclaration:prev = 0andcurr = 1, the values ofF(0)andF(1). At the start of an iteration for indexi,prevholdsF(i - 2)andcurrholdsF(i - 1). - Loop from 2 through
nwith the closed range2...n, so thatF(2)toF(n)are all computed. - Compute the new number as a constant:
let next = curr + prev. This isF(i). - Slide the window. Assign
prev = currfirst, thencurr = next. The order matters: the other way round would overwritecurrbefore it is saved. Withprev = 1andcurr = 2, the update givesprev = 2andcurr = 3. - Return
curr. After the loop it holdsF(n).
The solution in Swift
class Solution {
func fibonacci(_ n: Int) -> Int {
if n < 2 {
return n
}
var prev = 0, curr = 1
for i in 2...n {
let next = curr + prev
prev = curr
curr = next
}
return curr
}
}
Swift details worth knowing:
- The base case protects the range.
2...nis a closed range, and Swift requires its lower bound to be at most its upper bound. A call like2...1stops the program at run time. Because the method returns early forn < 2, the loop always hasn >= 2. - The unused loop variable.
iis only there to count iterations. Swift compiles this with a warning that it is never used; writingfor _ in 2...navoids the warning and works the same way. - Several variables in one declaration.
var prev = 0, curr = 1declares both asIntvariables. - Swift can swap in one statement, using a tuple:
(prev, curr) = (curr, prev + curr). It does the same job as the three lines in the loop body. - Overflow traps.
Intis 64 bits.F(92)is 7540113804746346429 and fits, butF(93)does not: computing it stops the program with a run-time error instead of wrapping around quietly. The wrapping operator&+would make it wrap, but then the values stop being Fibonacci numbers.
Tracing the loop
For n = 6, the loop starts with prev = 0 and curr = 1. Each pass produces the next number and moves the pair forward:
i = 2:next = 1, so the pair becomes (1, 1).i = 3:next = 2, so the pair becomes (1, 2).i = 4:next = 3, so the pair becomes (2, 3).i = 5:next = 5, so the pair becomes (3, 5).i = 6:next = 8, so the pair becomes (5, 8).
The loop ends and the method returns curr, which is 8, the value of F(6). The pair always holds two consecutive Fibonacci numbers, and that invariant is the whole idea of the solution.
Why not recursion
A direct translation of the definition looks like this:
var calls = 0
func fibRec(_ n: Int) -> Int {
calls += 1
if n < 2 { return n }
return fibRec(n - 1) + fibRec(n - 2)
}
Counting the calls shows the problem:
n | F(n) | Calls |
|---|---|---|
| 5 | 5 | 15 |
| 10 | 55 | 177 |
| 20 | 6765 | 21,891 |
| 30 | 832040 | 2,692,537 |
Each increase of 10 in n multiplies the calls by more than a hundred, because fibRec(n - 1) and fibRec(n - 2) each repeat most of the other's work. The loop computes every F(i) exactly once, which is why the lesson uses it.
Complexity
Time: O(n). The loop runs n - 1 times and each pass does an addition and two assignments. Space: O(1). The method uses prev, curr, next and the loop variable, whatever the value of n. The recursive definition, in contrast, makes an exponential number of calls.
Reading the result type
The method returns Int, so the answer is an exact integer and not a floating-point approximation. Unlike a language that silently switches to big numbers, Swift's fixed-width Int keeps arithmetic fast but limits this method to n up to 92. Tests with larger n would need a different representation, which is outside this lesson.
Edge cases and common mistakes
n = 0andn = 1. The early return gives 0 and 1. Without it,2...0would crash the program.n = 2. The loop runs once:next = 1 + 0 = 1, andcurrbecomes 1. The answer is 1.- Updating in the wrong order. Setting
curr = nextbeforeprev = currmakesprevequal to the new value, and the sequence goes wrong. - Returning
next. It is declared inside the loop body, so it isn't visible after the loop;currcarries the result out. - Using
2..<n. The half-open range stops one step early and returnsF(n - 1).
Testing
n = 0gives0,n = 1gives1.n = 2gives1,n = 3gives2,n = 4gives3.n = 10gives55.n = 50gives12586269025.n = 92gives7540113804746346429.
