LeetCode 2648 - Generate Fibonacci Sequence
The problem asks us to implement a generator function that yields numbers from the Fibonacci sequence. The Fibonacci sequence is defined recursively as Xn = Xn-1 + Xn-2 with the starting values X0 = 0 and X1 = 1.
Difficulty: 🟢 Easy
Topics: —
Solution
Problem Understanding
The problem asks us to implement a generator function that yields numbers from the Fibonacci sequence. The Fibonacci sequence is defined recursively as Xn = Xn-1 + Xn-2 with the starting values X0 = 0 and X1 = 1. Essentially, each number in the sequence is the sum of the two preceding numbers.
The input in this problem is not directly passed to the function, but rather the sequence is generated lazily using a generator object. The callCount in the examples represents the number of times the generator’s next() method is called. For example, if callCount = 5, the first five Fibonacci numbers [0,1,1,2,3] should be produced sequentially by the generator.
The constraints are small: 0 <= callCount <= 50, so performance is not a major concern. However, the solution must correctly yield numbers in order and handle zero calls, which should produce no output.
Edge cases include callCount = 0 (should produce nothing), callCount = 1 (should yield only 0), and the transition from the first two numbers to the standard recursive sum.
Approaches
The brute-force approach would be to precompute the Fibonacci sequence up to the maximum callCount and return numbers from an array. This works because the sequence is small and the number of calls is limited. The drawback is that it precomputes and stores numbers unnecessarily if the generator is called fewer times, and it is less memory-efficient.
The optimal approach leverages Python or Go generators, yielding Fibonacci numbers one at a time. Instead of storing the entire sequence, it keeps only the last two numbers in memory, computes the next number on demand, and yields it. This is efficient in both time and space and aligns perfectly with the lazy evaluation paradigm of generators.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(n) | Precomputes all Fibonacci numbers up to n and stores them in a list |
| Optimal | O(n) | O(1) | Uses a generator and stores only the last two numbers, yielding on demand |
Algorithm Walkthrough
- Initialize two variables
aandbwith the first two Fibonacci numbers0and1. These variables will track the last two numbers in the sequence. - Enter an infinite loop, since a generator can be called an arbitrary number of times. In each iteration, yield
aas the next Fibonacci number. - Update the two variables: set
atob, andbtoa + b. This effectively shifts the sequence forward by one step. - Repeat the loop, allowing the generator to lazily produce numbers each time
next()is called. - Because generators are lazy, no numbers are computed until the consumer requests them, handling the
callCount = 0edge case naturally.
Why it works: At each step, a always holds the next Fibonacci number to yield. The invariant is that a and b represent two consecutive Fibonacci numbers. Updating them in order ensures that the sequence remains correct indefinitely.
Python Solution
from typing import Generator
class Solution:
def fibGenerator(self) -> Generator[int, None, None]:
a, b = 0, 1
while True:
yield a
a, b = b, a + b
The Python implementation uses a generator function, which allows lazy evaluation of the Fibonacci sequence. We initialize the first two numbers and then enter an infinite loop. Each iteration yields the current number and updates the state for the next Fibonacci number. This directly corresponds to the algorithm steps above and automatically handles any number of calls, including zero.
Go Solution
package main
func fibGenerator() func() int {
a, b := 0, 1
return func() int {
result := a
a, b = b, a+b
return result
}
}
In Go, we implement a closure that keeps track of the last two Fibonacci numbers. Each call to the returned function yields the next number in the sequence. Unlike Python, Go does not have native generators, so we use a closure to mimic the same lazy evaluation behavior. Edge cases are handled naturally because the state is initialized correctly and updates on every call.
Worked Examples
Example 1: callCount = 5
| Step | a | b | Yielded |
|---|---|---|---|
| 1 | 0 | 1 | 0 |
| 2 | 1 | 1 | 1 |
| 3 | 1 | 2 | 1 |
| 4 | 2 | 3 | 2 |
| 5 | 3 | 5 | 3 |
Output: [0,1,1,2,3]
Example 2: callCount = 0
No calls to next(), no numbers are yielded. Output: []
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each next() call computes the next Fibonacci number in constant time, n calls total |
| Space | O(1) | Only two variables are maintained, regardless of the number of calls |
The generator approach ensures minimal memory usage while computing numbers on demand, which is ideal for lazy evaluation scenarios.
Test Cases
gen = Solution().fibGenerator()
assert [next(gen) for _ in range(5)] == [0,1,1,2,3] # Example 1
gen = Solution().fibGenerator()
assert [next(gen) for _ in range(0)] == [] # Example 2
gen = Solution().fibGenerator()
assert [next(gen) for _ in range(1)] == [0] # Single call
gen = Solution().fibGenerator()
assert [next(gen) for _ in range(2)] == [0,1] # First two numbers
gen = Solution().fibGenerator()
assert [next(gen) for _ in range(10)] == [0,1,1,2,3,5,8,13,21,34] # Larger sequence
| Test | Why |
|---|---|
| callCount = 5 | Validates standard small sequence |
| callCount = 0 | Validates zero calls edge case |
| callCount = 1 | Validates single element output |
| callCount = 2 | Validates first two elements correctness |
| callCount = 10 | Validates correctness for longer sequences |
Edge Cases
One important edge case is callCount = 0, which tests that the generator correctly handles no calls without errors. The Python generator and Go closure both naturally yield nothing until called, so this is handled automatically.
Another edge case is the transition from the first two numbers to the sum pattern. The first two numbers are 0 and 1, which do not follow the sum rule for initialization. The implementation correctly initializes these values so that the third number onwards follows the Fibonacci relation.
Finally, the generator must handle sequences longer than the first few elements without integer overflow in Python, which is handled automatically due to Python’s arbitrary-precision integers. In Go, integers can overflow for large sequences, but the problem constraints callCount <= 50 ensure that this will not happen, so no special handling is required.