2803. Factorial Generator 🔒
Problem Description
This problem asks you to create a generator function that produces the factorial sequence up to a given number n
.
The factorial of a number is the product of all positive integers from 1 up to that number. For example:
1! = 1
2! = 2 × 1 = 2
3! = 3 × 2 × 1 = 6
4! = 4 × 3 × 2 × 1 = 24
The special case is that 0! = 1
by definition.
Your generator function should:
- Take an integer
n
as input - Yield each factorial value in sequence from
1!
ton!
For instance, if n = 3
, the generator should yield:
- First yield:
1
(which is1!
) - Second yield:
2
(which is2! = 2 × 1
) - Third yield:
6
(which is3! = 3 × 2 × 1
)
If n = 0
, the generator should yield only 1
(since 0! = 1
).
The solution uses a simple iterative approach where it maintains a running product (ans
), multiplying it by each successive integer from 1 to n, and yielding the result at each step. This efficiently generates the factorial sequence without recalculating each factorial from scratch.
Intuition
The key insight is recognizing that each factorial builds upon the previous one. Rather than calculating each factorial from scratch (which would be inefficient), we can use the relationship that n! = n × (n-1)!
.
When generating the sequence 1!, 2!, 3!, ..., n!
, we notice:
1! = 1
2! = 2 × 1 = 2 × 1!
3! = 3 × 2 × 1 = 3 × 2!
4! = 4 × 3 × 2 × 1 = 4 × 3!
This pattern shows that each factorial is simply the previous factorial multiplied by the current number. This leads us to maintain a running product that we update at each step.
Since we need to output intermediate results (not just the final n!
), a generator function is perfect for this task. Generators allow us to produce values one at a time, pausing execution between yields, which matches our need to output each factorial in the sequence.
The approach becomes straightforward:
- Start with
ans = 1
(which represents0!
) - For each number
i
from 1 to n:- Multiply
ans
byi
to geti!
- Yield this value
- Continue to the next iteration
- Multiply
The special case of n = 0
is handled separately since there's no multiplication involved - we simply yield 1
directly.
This incremental calculation approach has optimal time complexity since each factorial is computed in constant time relative to the previous one, rather than recalculating all multiplications from the beginning.
Solution Approach
The implementation uses a generator function with a simple iterative approach to build the factorial sequence incrementally.
Step-by-step walkthrough:
-
Handle the special case: First, we check if
n === 0
. Since0! = 1
by definition and there's no sequence to generate, we simply yield1
and return. -
Initialize the accumulator: We start with
ans = 1
, which will store our running factorial value. -
Iterative multiplication: We loop from
i = 1
toi = n
:- Multiply the current
ans
byi
:ans *= i
- This gives us
i!
sinceans
previously held(i-1)!
- Yield the current factorial value
- Continue to the next iteration
- Multiply the current
Code implementation:
function* factorial(n: number): Generator<number> {
if (n === 0) {
yield 1;
}
let ans = 1;
for (let i = 1; i <= n; ++i) {
ans *= i;
yield ans;
}
}
Example execution for n = 3
:
- Initially:
ans = 1
- Iteration 1:
i = 1
,ans = 1 × 1 = 1
, yield1
- Iteration 2:
i = 2
,ans = 1 × 2 = 2
, yield2
- Iteration 3:
i = 3
,ans = 2 × 3 = 6
, yield6
The generator pauses after each yield
statement, allowing the caller to retrieve values one at a time using gen.next().value
. This lazy evaluation pattern is memory-efficient and provides values on-demand rather than computing and storing the entire sequence upfront.
Time Complexity: O(n)
- we perform n iterations
Space Complexity: O(1)
- we only maintain a single variable ans
regardless of input size
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with n = 4
to see how the generator produces the factorial sequence.
Initial Setup:
- Input:
n = 4
- We want to generate:
1!, 2!, 3!, 4!
- Initialize
ans = 1
Step-by-step execution:
Iteration 1 (i = 1): - Current ans: 1 - Calculate: ans = ans × i = 1 × 1 = 1 - Yield: 1 (this is 1!) - Generator pauses Iteration 2 (i = 2): - Current ans: 1 (from previous) - Calculate: ans = ans × i = 1 × 2 = 2 - Yield: 2 (this is 2!) - Generator pauses Iteration 3 (i = 3): - Current ans: 2 (from previous) - Calculate: ans = ans × i = 2 × 3 = 6 - Yield: 6 (this is 3!) - Generator pauses Iteration 4 (i = 4): - Current ans: 6 (from previous) - Calculate: ans = ans × i = 6 × 4 = 24 - Yield: 24 (this is 4!) - Generator completes
Usage example:
const gen = factorial(4);
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 6
console.log(gen.next().value); // 24
console.log(gen.next().done); // true
Notice how each factorial builds on the previous one - we never recalculate from scratch. The value 6
(which is 3!
) is obtained by multiplying the previous result 2
(which was 2!
) by 3
, demonstrating the efficiency of maintaining a running product.
Solution Implementation
1def factorial(n):
2 """
3 Generator function that yields factorial values from 1! up to n!
4
5 Args:
6 n: The maximum factorial to calculate
7
8 Yields:
9 Sequential factorial values: 1!, 2!, 3!, ..., n!
10 """
11 # Base case: 0! = 1
12 if n == 0:
13 yield 1
14 return
15
16 # Initialize the factorial accumulator
17 factorial_value = 1
18
19 # Calculate and yield each factorial from 1! to n!
20 for i in range(1, n + 1):
21 factorial_value *= i # Multiply by current number
22 yield factorial_value # Yield the current factorial value
23
24
25# Example usage:
26# gen = factorial(2)
27# next(gen) # 1 (which is 1!)
28# next(gen) # 2 (which is 2!)
29
1/**
2 * Class that generates factorial values from 1! up to n!
3 * Since Java doesn't have built-in generators like TypeScript,
4 * we implement an Iterator pattern to achieve similar functionality
5 */
6class FactorialGenerator implements Iterator<Integer> {
7 private int n; // Maximum factorial to calculate
8 private int current; // Current position in the sequence
9 private int factorialValue; // Current factorial value
10
11 /**
12 * Constructor for the factorial generator
13 * @param n - The maximum factorial to calculate
14 */
15 public FactorialGenerator(int n) {
16 this.n = n;
17 this.current = 0;
18 this.factorialValue = 1;
19 }
20
21 /**
22 * Check if there are more factorial values to generate
23 * @return true if more values exist, false otherwise
24 */
25 @Override
26 public boolean hasNext() {
27 // Handle base case: 0! = 1
28 if (n == 0) {
29 return current == 0;
30 }
31 // Check if we've reached the maximum
32 return current < n;
33 }
34
35 /**
36 * Get the next factorial value in the sequence
37 * @return The next factorial value
38 */
39 @Override
40 public Integer next() {
41 // Base case: 0! = 1
42 if (n == 0 && current == 0) {
43 current++;
44 return 1;
45 }
46
47 // Increment current position
48 current++;
49
50 // Calculate the factorial value for current position
51 factorialValue *= current; // Multiply by current number
52
53 // Return the current factorial value
54 return factorialValue;
55 }
56}
57
58/**
59 * Helper class to create the factorial generator
60 */
61class Factorial {
62 /**
63 * Factory method to create a factorial generator
64 * @param n - The maximum factorial to calculate
65 * @return An iterator that yields sequential factorial values: 1!, 2!, 3!, ..., n!
66 */
67 public static Iterator<Integer> factorial(int n) {
68 return new FactorialGenerator(n);
69 }
70}
71
72/**
73 * Example usage:
74 * Iterator<Integer> gen = Factorial.factorial(2);
75 * gen.next(); // 1 (which is 1!)
76 * gen.next(); // 2 (which is 2!)
77 */
78
1#include <iostream>
2#include <coroutine>
3#include <exception>
4
5/**
6 * Coroutine generator for yielding factorial values
7 */
8template<typename T>
9struct Generator {
10 struct promise_type {
11 T current_value;
12 std::exception_ptr exception;
13
14 Generator get_return_object() {
15 return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
16 }
17
18 std::suspend_always initial_suspend() { return {}; }
19 std::suspend_always final_suspend() noexcept { return {}; }
20
21 void unhandled_exception() {
22 exception = std::current_exception();
23 }
24
25 template<std::convertible_to<T> From>
26 std::suspend_always yield_value(From&& from) {
27 current_value = std::forward<From>(from);
28 return {};
29 }
30
31 void return_void() {}
32 };
33
34 std::coroutine_handle<promise_type> handle;
35
36 Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
37 ~Generator() {
38 if (handle)
39 handle.destroy();
40 }
41
42 // Move constructor and assignment
43 Generator(Generator&& other) noexcept : handle(std::exchange(other.handle, {})) {}
44 Generator& operator=(Generator&& other) noexcept {
45 if (this != &other) {
46 if (handle)
47 handle.destroy();
48 handle = std::exchange(other.handle, {});
49 }
50 return *this;
51 }
52
53 // Delete copy operations
54 Generator(const Generator&) = delete;
55 Generator& operator=(const Generator&) = delete;
56
57 // Iterator-like interface
58 bool next() {
59 handle();
60 return !handle.done();
61 }
62
63 T value() const {
64 return handle.promise().current_value;
65 }
66};
67
68/**
69 * Generator function that yields factorial values from 1! up to n!
70 * @param n - The maximum factorial to calculate
71 * @yields Sequential factorial values: 1!, 2!, 3!, ..., n!
72 */
73Generator<int> factorial(int n) {
74 // Base case: 0! = 1
75 if (n == 0) {
76 co_yield 1;
77 co_return;
78 }
79
80 // Initialize the factorial accumulator
81 int factorial_value = 1;
82
83 // Calculate and yield each factorial from 1! to n!
84 for (int i = 1; i <= n; ++i) {
85 factorial_value *= i; // Multiply by current number
86 co_yield factorial_value; // Yield the current factorial value
87 }
88}
89
90/**
91 * Example usage:
92 * auto gen = factorial(2);
93 * gen.next();
94 * gen.value(); // 1 (which is 1!)
95 * gen.next();
96 * gen.value(); // 2 (which is 2!)
97 */
98
1/**
2 * Generator function that yields factorial values from 1! up to n!
3 * @param n - The maximum factorial to calculate
4 * @yields Sequential factorial values: 1!, 2!, 3!, ..., n!
5 */
6function* factorial(n: number): Generator<number> {
7 // Base case: 0! = 1
8 if (n === 0) {
9 yield 1;
10 return;
11 }
12
13 // Initialize the factorial accumulator
14 let factorialValue: number = 1;
15
16 // Calculate and yield each factorial from 1! to n!
17 for (let i: number = 1; i <= n; ++i) {
18 factorialValue *= i; // Multiply by current number
19 yield factorialValue; // Yield the current factorial value
20 }
21}
22
23/**
24 * Example usage:
25 * const gen = factorial(2);
26 * gen.next().value; // 1 (which is 1!)
27 * gen.next().value; // 2 (which is 2!)
28 */
29
Time and Space Complexity
Time Complexity: O(n)
per complete iteration through all yielded values.
The generator function performs n
iterations in the for loop (from i = 1
to i = n
). Each iteration involves:
- One multiplication operation (
ans *= i
):O(1)
- One yield operation:
O(1)
Therefore, to generate all factorial values from 1!
to n!
, the total time complexity is O(n)
.
Note: If we consider individual next()
calls, each call has O(1)
amortized time complexity since the generator maintains its state between calls.
Space Complexity: O(1)
The generator function uses constant extra space:
- One variable
ans
to store the current factorial value - One loop counter variable
i
- The generator object itself maintains minimal state information
The generator yields values one at a time rather than storing all factorial values in memory, making it memory-efficient regardless of the value of n
.
Common Pitfalls
1. Forgetting to Handle the n=0 Case
A common mistake is omitting the special case for n = 0
. Without this check, the generator would yield nothing when n = 0
, which is incorrect since 0! = 1
.
Incorrect Implementation:
def factorial(n):
factorial_value = 1
for i in range(1, n + 1): # When n=0, range(1, 1) produces no iterations
factorial_value *= i
yield factorial_value
# Nothing is yielded when n=0!
Solution: Always include the base case check at the beginning of the function.
2. Integer Overflow for Large Values
While Python handles arbitrarily large integers automatically, in other languages or when dealing with very large factorials, overflow can occur. For example, 20! = 2,432,902,008,176,640,000
which exceeds the range of many integer types.
Solution:
- In Python, this is handled automatically
- In other languages, consider using BigInteger types or implement overflow checking
- Document the practical limits of your implementation
3. Yielding at the Wrong Time
Some developers might yield before multiplying, causing off-by-one errors:
Incorrect Implementation:
def factorial(n):
if n == 0:
yield 1
return
factorial_value = 1
yield factorial_value # This yields 1 before any multiplication!
for i in range(1, n + 1):
factorial_value *= i
yield factorial_value
# This yields n+1 values instead of n values
Solution: Ensure you multiply first, then yield, to get the correct sequence.
4. Not Understanding Generator Exhaustion
Once a generator is exhausted, it won't restart automatically:
gen = factorial(3)
values = list(gen) # [1, 2, 6]
more_values = list(gen) # [] - generator is exhausted!
Solution: Create a new generator instance when you need to iterate again, or store the results if you need to reuse them multiple times.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!