目录

题目描述

172. 阶乘后的零

题意分析

给定一个非负整数 n,问 n! 这个数写出来末尾有几个连续的零。

末尾的一个零,等价于这个数含有一个因子 10;而 10 只能由 2 和 5 相乘得到。所以末尾零的个数,就是 n! 的质因数分解里 2 的个数与 5 的个数取较小值。在连乘 1 到 n 的过程中,偶数出现的密度是 5 的倍数的两倍多,因子 2 永远富余,于是末尾零的个数完全由因子 5 的个数决定——这句话是整题的题眼,后面所有推导都建立在它上面。

约束信号有两处。一是 n 最大到 10000,n! 是一个上万位的天文数字,任何「先把阶乘算出来再数零」的思路在 64 位整数里第一步就溢出了,题目其实是在逼你只算个数、不算数值。二是进阶要求对数时间,暗示答案是一个每步把规模缩小常数倍的循环,而不是从 1 遍历到 n

边界包括 n = 00! = 1,答案 0)、n 小于 5(一个因子 5 都没有,答案 0),以及 25、125 这类高次幂本身携带多个因子 5 的情况。

解法:统计因子 5

核心思路

一个末尾零来自一对因子 2 × 5。在 1...n 中,因子 2 的数量一定多于因子 5,因此答案只取决于 n! 中有多少个因子 5,不需要计算阶乘本身。

5 的倍数至少贡献一个因子 5,共有 $\lfloor n/5 \rfloor$ 个;25 的倍数还会额外贡献一个,共有 $\lfloor n/25 \rfloor$ 个;125 的倍数再额外贡献一个。于是:

answer = n/5 + n/25 + n/125 + ...

每一项都使用整数除法。实现时让 n 反复除以 5,就能依次得到这些项,也避免直接计算 5^k 可能产生的溢出。

正确性说明:若一个数含有 k 个因子 5,它会在 5、25、……、$5^k$ 对应的前 k 层各被统计一次,恰好贡献 k 次;不含因子 5 的数不会进入任何一层。因此求和后不重不漏地得到 n! 中因子 5 的总数,也就是末尾零的个数。

解题步骤

  • 初始化 answer = 0
  • 每轮先令 n /= 5,得到当前层至少还能贡献一个因子 5 的数字个数。
  • 将当前 n 加入答案,直到 n 变为 0。
  • 返回累计结果。

例如 n = 125:三层贡献依次为 25、5、1,答案是 31。其中 25 的倍数被多统计一层,125 又被多统计一层,正好覆盖高次幂中的额外因子 5。

代码实现

class Solution {
    public int trailingZeroes(int n) {
        int answer = 0;
        while (n > 0) {
            n /= 5;
            answer += n;
        }
        return answer;
    }
}
func trailingZeroes(n int) int {
    answer := 0
    for n > 0 {
        n /= 5
        answer += n
    }
    return answer
}

复杂度分析

  • 时间复杂度:$O(\log_5 n)$,每轮都把 n 缩小为原来的五分之一。
  • 空间复杂度:$O(1)$,只使用常数个变量。

关键点总结

  • 不计算 n!,只统计决定末尾零数量的因子 5。
  • 5、25、125 等倍数要分层累计,不能只算 n / 5
  • 用反复整除 5 代替显式计算 5 的幂,代码更短且没有幂次溢出风险。

易错点总结

  • 只返回 n / 5:会漏掉 25、125 等数字额外携带的因子 5;n = 25 时正确答案是 6,不是 5。
  • 先累加再除以 5:第一次会把原始 n 加入答案;循环中必须先除后加。
  • 计算阶乘后再数零:阶乘很快溢出,且完全没有必要。
  • int p *= 5 枚举幂次:输入范围扩大后 p 可能溢出;反复修改 n 更稳妥。

相似题目

题目 难度 考察点
793. 阶乘函数后 K 个零 困难 在计数函数上二分反解
233. 数字 1 的个数 困难 按位分层统计出现次数
60. 排列序列 困难 阶乘进制与逐位确定