Data Structures · Binary Search on the Answer · A Field Guide

Preimage of the
factorial zeroes

How many x give x! exactly k trailing zeroes? You never touch a factorial. You count factors of five, notice the count only climbs, and binary-search a number line that has no array.

f(x) = k → 5 or 0
01 · Strip it down

Count the x that land on exactly k trailing zeroes

Let f(x) = the number of trailing zeroes in x!. Given k (up to 10⁹), count the non-negative integers x with f(x) = k — the preimage of k. For k=0: 0!,1!,2!,3!,4! all end in no zero, so the answer is 5. For k=5: the answer is 0 — and understanding why is the whole problem.


02 · Don't compute the factorial

Trailing zeroes are just factors of five

A trailing zero is a factor of 10 = 2 × 5. Inside 1 × 2 × … × x there are always more 2s than 5s, so fives are the bottleneck. Count them and you have the zero count — no giant number required:

f(x) = ⌊x/5⌋ + ⌊x/25⌋ + ⌊x/125⌋ + …

The extra terms matter because 25 = 5² hides two fives, 125 = 5³ hides three. So f(25) = 5 + 1 = 6, jumping straight past 5. That single jump is why k=5 has no preimage.


A staircase with a double step

Each step is exactly five wide, so every reachable height has five x's on it. But at powers of five the stair rises two levels at once — that skipped level has zero x's.

Search with no array

There's no list to index into. The candidates are the numbers 0…5k themselves, and you probe them by asking a yes/no question — binary search on the answer, not on memory.

Two edges, one subtraction

Find where level k begins and where the next level begins. The gap between those two edges is the block width — 5 if the level exists, 0 if it was skipped.


04 · The staircase

Flat fives, and the jump at 25

Plot f(x) for small x. Every level is five wide — until x = 25, where the count leaps from 4 to 6. Level 5 is a floor that no step ever lands on.

0 1 2 3 4 6 jumps 4→6 level 5 — empty 051015202530

So the preimage size is always 5 or 0 — never 1, 2, 3, 4. If a level exists, five consecutive x's sit on it; if a jump skipped it, none do.


05 · Binary search on the answer

Two lower-bound searches, subtracted

The x's with f(x) = k form one contiguous block, so count it by its edges: lowerBound(k) (first x with f(x) ≥ k) and lowerBound(k+1). Each edge is a binary search over the number line [0, 5(k+1)] — no array, just the monotonic f. Watch one lower-bound search below: f(mid) ≥ target keeps the left half (answer could be mid); f(mid) < target discards it.

Preimage counter

mid
–
f(mid)
–
f(mid) ≥ target?
–

Try k=5 (both edges land on 25 → answer 0) or k=0 (edges 0 and 5 → answer 5). Switch the edge dropdown to watch each half of the subtraction.


06 · Trace table

Three values of k

k lowerBound(k) lowerBound(k+1) difference the x's
0 0  (f(0)=0) 5  (f(5)=1) 5 0,1,2,3,4
5 25  (f(25)=6) 25  (f(25)=6) 0 none
6 25  (f(25)=6) 30  (f(30)=7) 5 25…29

For k=5, both edges snap to 25 because f never equals 5 — the subtraction returns 0 with no special-casing. The formula silently absorbs the skipped-level fact.


07 · The code

A helper, a boundary search, a subtraction

int preimageSizeFZF(int k) {
    return (int)(lowerBound(k + 1L) - lowerBound(k));
}

// first x with f(x) >= target
long lowerBound(long target) {
    long lo = 0, hi = 5L * (target + 1);
    while (lo < hi) {
        long mid = lo + (hi - lo) / 2;
        if (trailingZeroes(mid) >= target) hi = mid;
        else lo = mid + 1;
    }
    return lo;
}

long trailingZeroes(long x) {
    long c = 0;
    while (x > 0) { x /= 5; c += x; }   // x/5 + x/25 + x/125 + …
    return c;
}
Why 5·(target+1) as hi
~5 numbers per zero
To reach ~target zeroes, x is around 5·target — this bound comfortably clears it.
Why long everywhere
5·(10⁹+1) > INT_MAX
The search bound hits ~5×10⁹, overflowing int. Keep lo/hi/mid/target in long.

O(log² k) time, O(1) space. The outer search is O(log k) steps; each computes f(mid) in O(log₅ mid). Both edges together are two such searches.


08 · Why it's correct

Monotonic, so the predicate flips once

The invariant lowerBound(target) searches for the smallest x with f(x) ≥ target. Because (x+1)! = x! · (x+1) can only add factors of ten, f never decreases — so the predicate reads false…false true…true and flips exactly once. When f(mid) ≥ target, mid might be that first true, so keep it (hi = mid); otherwise discard it (lo = mid+1).

The preimage is one contiguous block precisely because f is monotonic. Its size is lowerBound(k+1) − lowerBound(k): where level k+1 starts, minus where level k starts. A skipped level makes both edges coincide, yielding 0 with no special case.


09 · Traps to avoid

Six ways it slips

Computing the factorial

x! overflows even long almost immediately. The problem exists to make you count factors instead.

Only ⌊x/5⌋

Misses the extra fives in 25, 125, 625… You need the full x/5 + x/25 + … sum.

Forgetting 0!

0! = 1, so f(0)=0. That's why k=0 has five x's (0–4), not four.

Assuming every k exists

Jumps at powers of five skip levels. f(24)=4, f(25)=6 — level 5 never appears.

Predicate f(mid) == k

Equality isn't monotonic (false…true…false), so binary search breaks. Use f(mid) ≥ target.

int for bounds

5·(k+1) reaches ~5×10⁹ > INT_MAX. Everything in the search must be long.


10 · The pattern

Binary search on the answer, not the array

There's no array here — the candidates are numbers in [0, 5k], probed by a monotonic predicate. That's binary search on the answer: choose a candidate, evaluate a monotone yes/no condition, discard half the numeric range. It's the clearest proof that binary search is a strategy for monotonic search spaces, not just sorted memory.

Counting a preimage
L = first x with g(x) ≥ k
R = first x with g(x) ≥ k+1; count = R − L
Same boundary engine as
first/last position
lower-bound on a monotone predicate — here the "array" is a formula

Two ideas stacked: replace an impossible computation with a factor count, then binary-search the monotone function's domain instead of enumerating inputs. That combination is what makes this more than a factorial puzzle.


11 · One month from now

Reconstruct it, don't memorize it

  1. Trailing zeroes come from 2×5 pairs; factorials have surplus 2s, so count fives.
  2. f(x) = x/5 + x/25 + x/125 + … — and it never decreases as x grows.
  3. So the x's with f(x)=k form one contiguous block; binary-search its edges.
  4. Count = lowerBound(k+1) − lowerBound(k), each a lower-bound search on [0, 5(k+1)] in long.
  5. Because levels are five wide or skipped entirely, the answer is always 5 or 0.
The transferable insight First replace an impossible large-number computation with a count of its prime factors. Then, once that derived function is monotonic, binary-search its numeric domain instead of enumerating inputs. Enumeration becomes two boundary probes.