Problem overview
The function factorial receives a non-negative integer n and returns n!, the product of every whole number from 1 to n. The lesson sets a budget of O(n) time and O(1) extra space.
In the example, n = 5, and 1 * 2 * 3 * 4 * 5 is 120, so the function returns 120. By convention 0! is 1, and the same code gives that answer without a special case.
Approach: build the product one factor at a time
Multiplying by hand, you would keep a running total and fold in each number in turn: start at 1, multiply by 2, then by 3, and so on up to n. The code does exactly that. A single variable holds the product so far, and a loop visits each factor once.
The loop runs n times, so the time is O(n). Only the product and the loop counter are stored, so the extra space is O(1).
Here is the state of fact for n = 4, one loop pass at a time: it starts at 1, becomes 1 when i is 1, then 2 when i is 2, then 6 when i is 3, and finally 24 when i is 4. Each value is the factorial of the i just processed, which is why the loop can stop at n and return what it holds.
Other approaches exist. A recursive function calls itself with n - 1 and multiplies the result by n, but it uses O(n) stack space. The loop avoids that and meets the O(1) space budget.
Step by step
- Declare the running product as
var fact = 1. It has to be avarbecause it changes on every pass, and it starts at 1 because 1 is the neutral element of multiplication: multiplying by it changes nothing. Starting at 0 would leave the answer stuck at 0. - Loop over the factors with
for (i in 1..n). The range1..nincludes both ends, soitakes every value from 1 ton. Kotlin declaresifor you, with novaror type. - Multiply inside the loop with
fact *= i, which is shorthand forfact = fact * i. - Return
factafter the loop, once every factor has been folded in.
The solution in Kotlin
class Solution {
fun factorial(n: Int): Int {
var fact = 1
for (i in 1..n) {
fact *= i
}
return fact
}
}
A few Kotlin details matter here:
- Ranges are inclusive.
1..ncontainsn. If you want to stop before the end, write1 until n. Usinguntilhere would leave out the last factor and return 24 forn = 5. - An empty range is fine. When
nis 0, the range1..0has no elements, so the loop body never runs and the function returns the initial 1. That is the correct value of0!. Intis 32 bits. It holds values up to 2,147,483,647, and overflow wraps around silently instead of raising an error.factorial(12)returns 479001600, butfactorial(13)returns 1932053504 rather than the true 6227020800, andfactorial(20)returns the negative number -2102132736. WithLongthe same loop is exact up to20!, which is 2432902008176640000. The lesson's signature usesInt, so the tests stay within its range.valwould not compile. Avalcan't be reassigned, sofact *= ineedsvar.
Complexity
Time: O(n). The loop body is one multiplication and runs once for each value from 1 to n.
Space: O(1). The function keeps fact and i and nothing that grows with n.
Edge cases and common mistakes
n = 0. Returns 1, because the loop doesn't run.n = 1. Returns 1, since the loop multiplies 1 by 1.- Starting
factat 0. Every product stays 0. - Forgetting the return. A Kotlin function with a declared
Intreturn type and noreturnon every path is a compile error. - Using
untilinstead of... The loop stops one factor early. - Large inputs. Beyond 12,
Intresults are wrong because of overflow, as shown above.
Testing
n = 5gives120.n = 0gives1.n = 1gives1.n = 12gives479001600.