A clear explanation of finding how many integers have exactly k trailing zeroes in their factorial.
Problem Restatement
Define:
f(x)as the number of trailing zeroes in:
x!We are given an integer k.
We need to return how many integers x satisfy:
f(x) == kThis count is called the size of the preimage of k.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer k |
| Output | Number of integers x such that f(x) == k |
f(x) | Number of trailing zeroes in x! |
| Important property | The answer is always 0 or 5 |
Function shape:
class Solution:
def preimageSizeFZF(self, k: int) -> int:
...Examples
Example 1:
k = 0We compute:
x | x! | Trailing zeroes |
|---|---|---|
0 | 1 | 0 |
1 | 1 | 0 |
2 | 2 | 0 |
3 | 6 | 0 |
4 | 24 | 0 |
5 | 120 | 1 |
Exactly five integers satisfy:
f(x) == 0So the answer is:
5Example 2:
k = 5There is no integer x such that:
f(x) == 5So the answer is:
0First Thought: Compute Factorials
A direct idea is:
- Compute factorials.
- Count trailing zeroes.
- Check which values produce exactly
k.
This quickly becomes impossible because factorials grow extremely fast.
We need a mathematical property instead.
Key Insight
Trailing zeroes in x! come from factors of:
10 = 2 * 5There are always more 2s than 5s in factorials, so the number of trailing zeroes equals the number of factors of 5.
The standard formula is:
f(x) = x // 5 + x // 25 + x // 125 + ...For example:
f(25) = 25 // 5 + 25 // 25
= 5 + 1
= 6Now observe an important property.
The function f(x) is monotonic:
x increases | f(x) |
|---|---|
Larger x | Same or larger |
But f(x) sometimes jumps.
For example:
x | f(x) |
|---|---|
24 | 4 |
25 | 6 |
The value 5 is skipped completely.
So some k values have no solutions.
When a solution exists, there are always exactly five consecutive values of x.
Why Exactly Five?
Suppose:
f(x) == kThen:
f(x + 1)
f(x + 2)
f(x + 3)
f(x + 4)usually remain the same until the next multiple of 5.
So solutions appear in blocks of five.
Therefore, the answer is only:
| Case | Answer |
|---|---|
k appears in f(x) | 5 |
k does not appear | 0 |
So we only need to check whether some x satisfies:
f(x) == kBinary Search
Since f(x) is monotonic, we can binary search for the smallest x such that:
f(x) >= kCall this value:
left_bound(k)Then:
| Expression | Meaning |
|---|---|
left_bound(k) | First x with f(x) >= k |
left_bound(k + 1) | First x with f(x) >= k + 1 |
The number of integers satisfying:
f(x) == kis:
left_bound(k + 1) - left_bound(k)This difference is always 0 or 5.
Algorithm
Define a helper:
count_zeroes(x)which computes trailing zeroes in x!.
Then define:
left_bound(target)using binary search.
Finally return:
left_bound(k + 1) - left_bound(k)Correctness
The number of trailing zeroes in x! equals the number of factors of 5 in the factorial expansion. Therefore:
f(x) = x // 5 + x // 25 + x // 125 + ...correctly computes the trailing zero count.
The function f(x) is monotonic because increasing x adds more factors to the factorial and cannot decrease the number of trailing zeroes.
For a target k, left_bound(k) returns the smallest integer x such that:
f(x) >= kSimilarly, left_bound(k + 1) returns the smallest integer where:
f(x) >= k + 1Therefore, every integer in the interval:
[left_bound(k), left_bound(k + 1))satisfies:
f(x) == kand no other integers do.
So the size of the preimage is exactly:
left_bound(k + 1) - left_bound(k)which the algorithm returns.
Complexity
Let M be the binary search range.
| Metric | Value | Why |
|---|---|---|
| Time | O(log M * log M) | Binary search calls count_zeroes, which itself runs in logarithmic time |
| Space | O(1) | Only integer variables are used |
A safe upper bound is:
5 * (k + 1)because each multiple of 5 contributes at least one trailing zero.
Implementation
class Solution:
def preimageSizeFZF(self, k: int) -> int:
def count_zeroes(x: int) -> int:
total = 0
while x > 0:
x //= 5
total += x
return total
def left_bound(target: int) -> int:
left = 0
right = 5 * (target + 1)
while left < right:
mid = (left + right) // 2
if count_zeroes(mid) < target:
left = mid + 1
else:
right = mid
return left
return left_bound(k + 1) - left_bound(k)Code Explanation
The helper computes trailing zeroes:
def count_zeroes(x: int) -> int:Each division counts how many numbers contribute extra factors of 5:
x //= 5
total += xThe binary search finds the first value where:
f(x) >= targetIf the current midpoint has too few zeroes:
if count_zeroes(mid) < target:
left = mid + 1Otherwise, we move leftward to find the first valid position:
else:
right = midFinally, the number of exact matches is:
left_bound(k + 1) - left_bound(k)Testing
def run_tests():
s = Solution()
assert s.preimageSizeFZF(0) == 5
assert s.preimageSizeFZF(1) == 5
assert s.preimageSizeFZF(2) == 5
assert s.preimageSizeFZF(3) == 5
assert s.preimageSizeFZF(4) == 5
assert s.preimageSizeFZF(5) == 0
assert s.preimageSizeFZF(6) == 5
assert s.preimageSizeFZF(10) == 5
assert s.preimageSizeFZF(11) == 0
print("all tests passed")
run_tests()Test coverage:
| Test | Why |
|---|---|
k = 0 | Smallest valid preimage |
k = 1..4 | Normal five-value blocks |
k = 5 | First skipped value |
k = 6 | First value after a jump |
| Larger skipped values | Confirms jump behavior |