Write a function that, given a non-negative \(n\) where \(n \in \mathbb{Z}\), returns the number of trailing zeroes in \(n!\) - without computing the factorial itself.

For example, \(10! = 3628800\) which has two trailing zeroes, therefore \(f(10) = 2\).

\(21! = 51090942171709440000\) which has four trailing zeroes, therefore \(f(21) = 4\).

Solution

To solve this challenge, we will first write out some values for \(n!\) and to see if a pattern arises.

\(n\) \(n!\)
\(1\) \(1\)
\(2\) \(2\)
\(3\) \(6\)
\(4\) \(24\)
\(5\) \(120\)
\(6\) \(720\)
\(7\) \(5040\)
\(8\) \(40320\)
\(9\) \(362880\)
\(10\) \(3628800\)
\(11\) \(39916800\)
\(12\) \(479001600\)
\(13\) \(6227020800\)
\(14\) \(87178291200\)
\(15\) \(1307674368000\)
\(16\) \(20922789888000\)
\(17\) \(355687428096000\)
\(18\) \(6402373705728000\)
\(19\) \(121645100408832000\)
\(20\) \(2432902008176640000\)
\(21\) \(51090942171709440000\)
\(22\) \(1124000727777607680000\)
\(23\) \(25852016738884976640000\)
\(24\) \(620448401733239439360000\)
\(25\) \(15511210043330985984000000\)

As we might expect, the factorial function grows exceedingly fast. However, let's now take a look at how many trailing zeroes there are for each value of \(n\):

\(n\) Trailing zeroes in \(n!\)
\(1\) \(0\)
\(2\) \(0\)
\(3\) \(0\)
\(4\) \(0\)
\(5\) \(1\)
\(6\) \(1\)
\(7\) \(1\)
\(8\) \(1\)
\(9\) \(1\)
\(10\) \(2\)
\(11\) \(2\)
\(12\) \(2\)
\(13\) \(2\)
\(14\) \(2\)
\(15\) \(3\)
\(16\) \(3\)
\(17\) \(3\)
\(18\) \(3\)
\(19\) \(3\)
\(20\) \(4\)
\(21\) \(4\)
\(22\) \(4\)
\(23\) \(4\)
\(24\) \(4\)
\(25\) \(6\)

Now the more mathematically inclined among you may try to find this sequence on the OEIS, and doing so would yield the sequence A027868 "Number of trailing zeros in \(n!\)", where the formula to solve for \(f(n)\) is listed in both summation and closed form. However that's cheating, so forget that for now. We're going to derive a solution ourselves.

We note that for each multiple of \(5\), the number of trailing zeroes in \(n!\) seems to initially increase by 1. We go from \(0 \to 1\) when we go from \(n=4 \to n=5\). Next we go from \(1 \to 2\) when going from \(n=9 \to n=10\), and so on.

However this pattern breaks when we reach \(n=25\), where we jump from \(4\) trailing zeroes to \(6\). What's going on here?

Well, let's write out the product form of some of these values. We'll focus on the values where the number of trailing zeroes increases, as well as the value of \(n-1\) and \(n+1\).

\[ \begin{align} 4! &= 4 \cdot 3 \cdot 2 \cdot 1 \\ 5! &= 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \\ 6! &= 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \\ \dots \\ 9! &= 9 \cdot 8 \cdot 7 \cdot 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \\ 10! &= 10 \cdot 9 \cdot 8 \cdot 7 \cdot 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \\ 11! &= 11 \cdot 10 \cdot 9 \cdot 8 \cdot 7 \cdot 6 \cdot 5 \cdot 4 \cdot 3 \cdot 2 \cdot 1 \\ \dots \\ 14! &= 14 \cdot 13 \cdot 12 \cdot 11 \cdot 10 \cdot 9 \cdot 8 \cdot 7 \cdot \dots \\ 15! &= 15 \cdot 14 \cdot 13 \cdot 12 \cdot 11 \cdot 10 \cdot 9 \cdot 8 \cdot \dots \\ 16! &= 16 \cdot 15 \cdot 14 \cdot 13 \cdot 12 \cdot 11 \cdot 10 \cdot \dots \end{align} \]

At first glance, the product forms don't reveal a dramatic pattern. However, if we express these factorials in their prime factorisation form, an interesting pattern emerges.

\[ \begin{align} 4! &= 2^3 \cdot 3 \\ 5! &= 2^3 \cdot 3 \cdot 5 \\ 6! &= 2^4 \cdot 3^2 \cdot 5 \\ \dots \\ 9! &= 2^7 \cdot 3^4 \cdot 5 \cdot 7 \\ 10! &= 2^8 \cdot 3^4 \cdot 5^2 \cdot 7 \\ 11! &= 2^8 \cdot 3^4 \cdot 5^2 \cdot 7 \cdot 11 \\ \dots \\ 14! &= 2^{11} \cdot 3^5 \cdot 5^2 \cdot 7^2 \cdot 11 \cdot 13 \\ 15! &= 2^{11} \cdot 3^6 \cdot 5^3 \cdot 7^2 \cdot 11 \cdot 13 \\ 16! &= 2^{15} \cdot 3^6 \cdot 5^3 \cdot 7^2 \cdot 11 \cdot 13 \end{align} \]

We can now begin to see that the values for where the trailing zero count increases, so too does the exponent \(x\) in the \(5^x\) term. In fact, the key to cracking this challenge is the exponent \(x\) in the \(5^x\) term. In other words, the number of trailing zeros is exactly the number of factors of \(5\) in the factorial. This makes sense because as soon as we hit \(n=5\), we now have at least one factor of \(2\) and at least one factor of \(5\), in other words \(2 \cdot 5\), or \(10\). For all \(n \geq 5\), the result of \(n!\) will always have at least one trailing zero! A factor of \(10\) will always be hidden in there.

This also explains the jump from \(f(n)=4\) when \(n=24\), to \(f(n)=6\) when \(n=25\). This is because \(25\) is equal to \(5^2\), so we not only introduce one factor of \(5\), but two. Writing out the prime factorisation of \(24!\) and \(25!\) helps us see this more clearly:

\[ \begin{align} 24! = 2^{22} \cdot 3^{10} \cdot \boxed{5^4} \cdot 7^3 \cdot 11^2 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \\ 25! = 2^{22} \cdot 3^{10} \cdot \boxed{5^6} \cdot 7^3 \cdot 11^2 \cdot 13 \cdot 17 \cdot 19 \cdot 23 \end{align} \]

Since the exponent increased by \(2\), so too does the number of trailing zeroes.

The way to solve this challenge is to remove each power of \(5\), counting how many times we did so. This means that the number of trailing zeroes is equal to the following series:

\[ f(n) = \left\lfloor \frac{n}{5} \right\rfloor + \left\lfloor \frac{n}{25} \right\rfloor + \left\lfloor \frac{n}{125} \right\rfloor + \dots + \left\lfloor \frac{n}{k} \right\rfloor \]

Where \(k\) is the largest integer power of \(5\) that does not exceed \(n\). More succinctly, it can be expressed as the following summation:

\[ f(n) =\sum_{k=1}^{\left\lfloor \log_{5}(n) \right\rfloor} \left\lfloor \frac{n}{5^k} \right\rfloor \tag{1} \]

Let's try an example of this. We know that \(5! = 12\underline{0}\), so \(f(5) = 1\). Plugging in our values to the series gives:

\[ \begin{align} f(5) &= \left\lfloor \frac{5}{5^1} \right\rfloor \\ &= \left\lfloor \frac{5}{5} \right\rfloor \\ &= 1 \end{align} \]

Since the next power of \(5\) (which would be \(25\)) exceeds \(n=5\), we just have the one term which evaluates to \(1\).

Now we'll try \(25!\), which equals \(15511210043330985984\underline{000000}\). This has \(6\) leading zeroes, and so \(f(25) = 6\). We'll write out the series, stopping at the point where we the power of \(5\) exceeds \(25\):

\[ \begin{align} f(25) &= \left\lfloor \frac{25}{5^1} \right\rfloor + \left\lfloor \frac{25}{5^2} \right\rfloor \\ &= \left\lfloor \frac{25}{5} \right\rfloor + \left\lfloor \frac{25}{25} \right\rfloor \\ &= 5 + 1 \\ &= 6 \end{align} \]

One way (among many) to achieve this in C# would be like so:

int TrailingZeroes(int n)
{
    int result = 0;

    for (int k = 1; ; k++)
    {
        int power = (int)Math.Pow(5, k);
        if (power > n)
            break;
        
        result += n / power;
    }
    
    return result;
}

Alternatively, to keep the break condition within the definition of the for loop, we can iterate \(\lfloor \log_{5}(n) \rfloor\) times inclusive, as seen in \((1)\). The following implementation is effectively identical, although it does require an addition Math.Log call:

int TrailingZeroes(int n)
{
    int ubound = (int)Math.Log(n, 5);
    int result = 0;

    for (int k = 1; k <mark><=</mark> ubound; k++)
    {
        result += (int)(n / Math.Pow(5, k));
    }
    
    return result;
}

If you're content with this solution, go ahead and call it here. However we're not done quite yet. There exists another solution!

First, consider that any integer \(n\) can be expressed in base \(5\) as:

\[ n = a_{0} + a_{1} \cdot 5 + a_{2} \cdot 5^2 + \dots + a_{k} \cdot 5^k \]

The sum of the digits1 in base \(5\) can therefore be expressed as:

\[ s_{5}(n) = a_{0} + a_{1} + a_{2} + \dots + a_{k} \]

If we perform integer division by \(5\), notice that we find:

\[ \left\lfloor \frac{n}{5} \right\rfloor = a_{1} + a_{2} \cdot 5 + \dots + a_{k} \cdot 5^{k-1} \]

Similarly:

\[ \left\lfloor \frac{n}{5^2} \right\rfloor = a_{2} + a_{3} \cdot 5 + \dots + a_{k} \cdot 5^{k-2} \]

Since the trailing zero count is defined to be of the series \((1)\), we can substitute the expressions we have:

\[ f(n) = \sum_{i=1}^k \left( a_{i} + a_{i + 1} \cdot 5 + a_{i + 2} \cdot 5^2 + \dots + a_{k} \cdot 5^{k - i} \right) \]

For each \(i\) from \(1\) to \(k\), the inner sum is a geometric series:

\[ 1 + 5 + 5^2 + \dots + 5^{i - 1} = \frac{5^i - 1}{5 - 1} = \frac{5^i - 1}{4} \]

Therefore \(f(n)\) can be expressed as:

\[ f(n) = \sum_{i = 1}^k a_{i} \left( \frac{5^i - 1}{4} \right) \]

Multiplying both sides by \(4\) gives:

\[ 4f(n) = \sum_{i = 1}^k a_{i} \left( 5^i - 1 \right) \]

Notice that:

  • \(\sum_{i=1}^k a_{i} \cdot 5^i = n - a_{0}\), since \(n = a_{0} + \sum_{i=1}^k a_{i} \cdot 5^i\)
  • and \(\sum_{i=1}^k a_{i} = s_{5}(n) - a_{0}\)

Thus:

\[ 4f(n) = (n - a_{0}) - (s_{5}(n) - a_{0}) = n - s_{5}(n) \]

Finally, dividing by \(4\) gives us:

\[ f(n) = \frac{n - s_{5}(n)}{4} \tag{2} \]

This also leads to an interesting identity:

\[ n = s_{5}(n) + 4 \left( \left\lfloor \frac{n}{5} \right\rfloor + \left\lfloor \frac{n}{25} \right\rfloor + \left\lfloor \frac{n}{125} \right\rfloor + \dots \right) \]

Which makes sense, because every power \(5^k\) is simply one more than a multiple of \(4\). For example \(5^1 = 5 = 4 + 1\), \(5^2 = 25 = 4(6) + 1\), \(5^3 = 4(31) + 1\), etc.

A C# function to sum the digits in base 5 looks something like this:

int SumOfBase5Digits(int n)
{
    int sum = 0;
    while (n > 0)
    {
        sum += n % 5;
        n /= 5;
    }
    return sum;
}

Which means the TrailingZeroes function can be essentially one-lined:

int TrailingZeroes(int n)
{
    return (n - SumOfBase5Digits(n)) / 4;
}

I don't believe it's possible to express the solution without some kind of loop (either the summation of \(\frac{n}{5^k}\) terms given by \((1)\), or the sum of the digits given by \((2)\)). This seems to imply that the lower bound is \(\Omega(\log n)\). However, if anyone stumbles across a solution that can be performed in \(\Omega(1)\) time, please do let me know!

As I was solving this challenge myself (which I always do to ensure it is in fact solvable), I also managed to write up an incredibly cursed one-liner. To anyone who wishes to understand it: Good. Luck.

int TrailingZeroes(int n) => Enumerable.Range(1, n).SelectMany(n => Enumerable.Range(2, n-1).Aggregate((rem: n, facs: new List<int>()), (s, i) => { while(s.rem % i == 0){ s.facs.Add(i); s.rem /= i; } return s; }).facs).Count(n => n == 5);

  1. Fun fact: the sum of the digits of \(n\) in base \(5\) also have an OEIS sequence: A053824.