Factorial Trailing Zeroes
Given an integer n, return the number of trailing zeroes in n! (n factorial).
Follow up: Could you write a solution that works in logarithmic time complexity?
Examples:
Example 1:
Input: n = 3
Output: 0
Explanation: 3! = 6, no trailing zero.
Example 2:
Input: n = 5
Output: 1
Explanation: 5! = 120, one trailing zero.
Example 3:
Input: n = 10
Output: 2
Explanation: 10! = 3628800, two trailing zeros. (From 5 and 10)
Example 4:
Input: n = 0
Output: 0
Explanation: 0! = 1, no trailing zero.
Constraints:
0 <= n <= 104(For the brute-force approach, calculating n! might overflow for larger n, but the optimal solution handles this).
Function Signature (Python):
class Solution:
def trailingZeroes(self, n: int) -> int:
# Your code here
pass
Related Python Concepts
Hint
Think about what creates a trailing zero in a number.
- Source of Zeroes: A trailing zero is created by a factor of 10. What are the prime factors of 10? (2 and 5).
- Limiting Factor: In the prime factorization of
n!, will there be more factors of 2 or factors of 5? The number of 10s you can form (and thus the number of trailing zeroes) is limited by the count of the scarcer prime factor. - Counting Factors of 5:
- Numbers like 5, 10, 15, 20, 25, ... contribute factors of 5.
- How many numbers from 1 to
nare multiples of 5? (n // 5) - What about numbers like 25, 50, 75, 125? They contribute *more than one* factor of 5 (e.g., 25 = 5\*5, 125 = 5\*5\*5). How can you account for these extra factors?
- Logarithmic Approach: The process of repeatedly dividing
nby 5 (and powers of 5) to count these factors inherently leads to a logarithmic number of steps.
Solution Explanation & Interview Strategy
The Goal: We want to find out how many zeroes are at the end of a factorial number. For example, 5! = 120, which has one trailing zero. 10! = 3,628,800, which has two trailing zeros.
A trailing zero is formed every time we have a factor of 10. Since 10 = 2 * 5, we need to count pairs of 2s and 5s in the prime factorization of n!. There will always be more factors of 2 than 5, so the number of trailing zeros is determined by the number of factors of 5.
Approach 1: Brute Force (Calculate Factorial, then Count Zeroes) - Not Recommended
A naive approach would be to first calculate n!, then convert it to a string and count trailing zeroes, or repeatedly divide by 10. However, n! grows extremely fast and will quickly overflow standard integer types for even moderately small n (e.g., 21! is already too large for a 64-bit integer). This approach is generally not feasible or what the interviewer is looking for unless n is very small.
class Solution:
def trailingZeroes_brute(self, n: int) -> int:
if n < 0: return 0 # Or raise error, factorial undefined for negative
if n == 0: return 0 # 0! = 1, no trailing zeros
factorial = 1
for i in range(1, n + 1):
factorial *= i
count = 0
# This part will fail for large factorials due to precision or conversion limits
while factorial > 0 and factorial % 10 == 0:
count += 1
factorial //= 10
return count
Complexity Analysis (Theoretical, if large numbers were manageable):
Time Complexity: O(N) to calculate the factorial, but this is an oversimplification. Multiplying very large numbers is not an O(1) operation.
Space Complexity: O(log(N!)), which simplifies to O(N log N).
Deep Dive: Why is the space complexity O(N log N)?
- The space needed to store any number
Xis proportional to its number of digits, which is `O(log X)`. - Therefore, storing the number `N!` requires `O(log(N!))` space.
- Using the logarithm property `log(a*b) = log(a) + log(b)`, we get `log(N!) = log(1) + log(2) + ... + log(N)`.
- This sum is mathematically approximated by `N * log(N)` (related to Stirling's Approximation). Thus, the space complexity is `O(N log N)`, which is very large.
Interview Note: Quickly mention this approach and its severe limitations (overflow) to show you've considered it, then move to the optimal solution. Python handles arbitrarily large integers, so it might "work" for slightly larger N than C++/Java, but the principle of overflow is important, and the method is still inefficient.
Approach 2: Counting Factors of 5 (Optimal - Logarithmic Time)
As established, the number of trailing zeros is determined by the number of factors of 5 in the prime factorization of n!. To count these, we sum:
- The number of multiples of 5 up to
n(i.e.,n // 5). These contribute at least one factor of 5. - The number of multiples of 25 up to
n(i.e.,n // 25). These contribute an additional factor of 5 (since 25 = 5 * 5, and one factor was already counted byn // 5). - The number of multiples of 125 up to
n(i.e.,n // 125). These contribute yet another factor of 5. - And so on, for all powers of 5 less than or equal to
n.
(n // 5) + (n // 25) + (n // 125) + ... until the term n // (5k) becomes 0.
class Solution:
def trailingZeroes_optimal(self, n: int) -> int:
if n < 0:
return 0 # Or handle as an error
count_of_fives = 0
power_of_5 = 5
while n // power_of_5 >= 1:
count_of_fives += n // power_of_5
power_of_5 *= 5
return count_of_fives
# Alternative compact loop for the optimal solution:
class Solution:
def trailingZeroes_optimal_v2(self, n: int) -> int:
count = 0
while n >= 5:
n //= 5
count += n
return count
# Example Usage:
# sol = Solution()
# print(sol.trailingZeroes_optimal(25)) # Output: 6 (25//5=5, 25//25=1. 5+1=6)
# print(sol.trailingZeroes_optimal_v2(25)) # Output: 6
count = 0.n = 25.25 >= 5is true.n = 25 // 5 = 5.count = 0 + 5 = 5.
n = 5.5 >= 5is true.n = 5 // 5 = 1.count = 5 + 1 = 6.
n = 1.1 >= 5is false. Loop terminates.- Return
count(which is 6).
This compact loop (`trailingZeroes_optimal_v2`) correctly sums n/5 + n/25 + n/125 + .... When n is first divided by 5, `n` becomes `n_orig/5`. Adding this new `n` to `count` is equivalent to adding `n_orig/5`. In the next iteration, if `n_orig/5 >= 5`, then `n` (which is currently `n_orig/5`) is divided by 5 again, becoming `n_orig/25`. Adding this to `count` is equivalent to adding `n_orig/25`.
Complexity Analysis
Time Complexity: O(log N)
The time complexity is logarithmic, which is extremely efficient. This is because the algorithm does not iterate from 1 to n. Instead, the loop's controlling variable is changed by a factor of 5 in each step. Let's analyze both optimal code versions to see how this works.
Version 1: Using `power_of_5`
In this method, the `power_of_5` variable grows exponentially: 5, 25, 125, 625, and so on. The loop continues as long as this value is less than or equal to `n`. The number of iterations, `k`, required for `5^k` to exceed `n` is `log₅(n)`. This is a classic logarithmic-time algorithm.
Version 2: Dividing `n` directly
This more compact version achieves the same result. The input `n` is repeatedly divided by 5. The number of times you can divide a number by 5 until it becomes less than 5 is, by definition, `log₅(n)`. For example, for `n=1000`, the loop runs for:
n = 1000 // 5 = 200n = 200 // 5 = 40n = 40 // 5 = 8n = 8 // 5 = 1(loop terminates after this iteration)
This takes only 4 steps, whereas `log₅(1000) ≈ 4.29`.
Space Complexity: O(1)
In both versions, we only use a few variables (a counter, `n`, and maybe `power_of_5`). The space required does not grow with the input size `n`, so it is constant.
Key Takeaways for Interviews:
- Identify the Core Insight: The number of trailing zeros is dictated by the number of factors of 5 (since 10 = 2\*5 and factors of 2 are more abundant).
- Avoid Brute Force: Explain why calculating
n!directly is not feasible due to overflow and inefficiency. - Explain the Factor Counting Logic: Clearly articulate how multiples of 5, 25, 125, etc., contribute factors of 5. The sum
n/5 + n/25 + n/125 + ...is the key. - Logarithmic Solution: Demonstrate the iterative approach that achieves O(log N) time complexity. The `_v2` version is particularly elegant.
- Handle Edge Cases: Consider
n=0(0! = 1, 0 zeroes) and negativen(usually 0 zeroes or error, depending on problem spec).