The Leetcode problem Preimage Size of Factorial Zeroes Function asks: Given a non-negative integer k, return the number of non-negative integers x such that the number of trailing zeroes in x! (x factorial) is exactly k.
In other words, for a given k, how many values of x satisfy f(x) == k where f(x) is the number of trailing zeros in x!?
f(x) is non-decreasing: as x increases, f(x) increases or stays the same, but never decreases.k (0 ≤ k ≤ 109).x such that f(x) == k.
The first step is to understand how trailing zeros are formed in factorials. Each trailing zero in x! comes from a factor of 10, which is produced by multiplying 2 and 5. Since there are usually more 2s than 5s, the number of 5s in the prime factorization of x! determines the number of trailing zeros.
For a given x, the number of times 5 divides x! is:
f(x) = x // 5 + x // 25 + x // 125 + ...
A brute-force approach would be to compute f(x) for every x up to a very large number, but this is inefficient for large k. Instead, we recognize that the function f(x) is non-decreasing and often "flat" (i.e., the same value for several consecutive x). So, for a given k, there may be a block of x values where f(x) == k. Our task is to count how many such x there are.
This suggests using binary search to efficiently find the range of x that map to a given k.
The main idea is to use binary search to find the smallest x such that f(x) == k and the smallest x such that f(x) == k+1. The difference between these two values gives the number of x for which f(x) == k.
x, f(x) is the number of trailing zeroes in x!.x // 5, x // 25, x // 125, etc.x such that f(x) == k.x such that f(x) == k+1.right - left.f(x) is non-decreasing, all x with f(x) == k are consecutive.
In summary, we use binary search to efficiently find the range of x for which f(x) == k.
Example: Let’s take k = 5.
f(x) for x = 0, 1, ..., 30:
f(25) = 6f(24) = 4f(25) = 6x such that f(x) = 5.x where f(x) == 5 (let’s say it’s x1).
x where f(x) == 6 (let’s say it’s x2).
x2 - x1.
k = 5, the answer is 0, because there are no x for which f(x) == 5 (it jumps from 4 to 6).
k = 0, f(0) = 0, f(1) = 0, f(2) = 0, f(3) = 0, f(4) = 0, f(5) = 1. So, there are 5 values (0,1,2,3,4) for which f(x) == 0.
x up to a very large number (can be up to 5*k), compute f(x). This is O(N log N), which is infeasible for large k.
f(x) computation is O(log x), but since binary search only does this a few times, total time is O(log2 N).
The key insight is that the number of trailing zeros in x! increases in blocks, and for any given k, the x values with f(x) == k are consecutive and form a block of at most 5 numbers. By using binary search to find the boundaries of this block, we efficiently compute the answer even for very large k. This approach is both elegant and highly efficient compared to brute-force methods.
class Solution:
def preimageSizeFZF(self, k: int) -> int:
def zeta(x):
res = 0
while x:
x //= 5
res += x
return res
def left_bound(k):
lo, hi = 0, 5 * k + 5
while lo < hi:
mid = (lo + hi) // 2
if zeta(mid) < k:
lo = mid + 1
else:
hi = mid
return lo
return left_bound(k + 1) - left_bound(k)
class Solution {
public:
int zeta(long long x) {
int res = 0;
while (x) {
x /= 5;
res += x;
}
return res;
}
int left_bound(int k) {
long long lo = 0, hi = 5LL * k + 5;
while (lo < hi) {
long long mid = (lo + hi) / 2;
if (zeta(mid) < k)
lo = mid + 1;
else
hi = mid;
}
return (int)lo;
}
int preimageSizeFZF(int k) {
return left_bound(k + 1) - left_bound(k);
}
};
class Solution {
private int zeta(long x) {
int res = 0;
while (x > 0) {
x /= 5;
res += x;
}
return res;
}
private int leftBound(int k) {
long lo = 0, hi = 5L * k + 5;
while (lo < hi) {
long mid = (lo + hi) / 2;
if (zeta(mid) < k) {
lo = mid + 1;
} else {
hi = mid;
}
}
return (int)lo;
}
public int preimageSizeFZF(int k) {
return leftBound(k + 1) - leftBound(k);
}
}
var preimageSizeFZF = function(k) {
function zeta(x) {
let res = 0;
while (x > 0) {
x = Math.floor(x / 5);
res += x;
}
return res;
}
function leftBound(k) {
let lo = 0, hi = 5 * k + 5;
while (lo < hi) {
let mid = Math.floor((lo + hi) / 2);
if (zeta(mid) < k) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
return leftBound(k + 1) - leftBound(k);
};