LeetCode 172. 阶乘后的零:从暴力到最优,拆解解题核心

0 阅读5分钟

刷LeetCode时,遇到「阶乘后的零」这道题,初看简单,实则藏着优化的小技巧。今天就从题目本身出发,一步步拆解两种解题思路,对比暴力解法和最优解法的差异,帮大家吃透这道经典题目,也掌握一类数学问题的解题逻辑。

一、题目解读:读懂「尾随零」的本质

题目很简单:给定一个整数n,返回n!(n的阶乘)结果中尾随零的数量。提示里明确了n! = n × (n-1) × (n-2) × ... × 3 × 2 × 1。

这里有个关键误区:很多人会以为尾随零的数量是由阶乘中10的个数决定的——确实没错,但10是2×5的乘积,而阶乘中2的数量永远比5多(比如每两个数就有一个是2的倍数,每五个数才有一个是5的倍数)。所以,尾随零的数量,本质上是阶乘中质因数5的个数

搞懂这一点,解题就成功了一半。接下来我们看两种解法,从基础到进阶。

二、解法一:暴力枚举,逐个统计5的个数

最直观的思路:遍历从1到n的每一个数,统计每个数中包含的质因数5的个数,最后累加总和,就是尾随零的数量。

对应代码如下(TypeScript):

function trailingZeroes_1(n: number): number {
  let ans = 0;
  for (let i = 1; i <= n; i++) {
    let temp = 0;
    let j = i;
    while (j % 5 === 0) {
      temp++;
      j = Math.floor(j / 5);
    }
    ans += temp;
  }
  return ans;
};

代码拆解

  • 外层for循环:遍历1到n的每一个数i,逐个分析每个数包含的5的个数。

  • 内层while循环:对于当前数i(用j临时存储,避免修改i),判断是否能被5整除;如果能,就计数temp加1,同时将j除以5(继续判断是否有多个5,比如25=5×5,需要统计2个5)。

  • ans累加:将每个数的temp(5的个数)累加到结果中,最终返回ans。

优缺点分析

优点:逻辑简单,容易理解,适合新手入门,能快速想到这种思路。

缺点:效率较低。当n很大时(比如n=10^5),for循环要执行n次,每个数又可能执行多次while循环,时间复杂度是O(n×log₅n),会出现超时情况。

三、解法二:数学优化,直接计算5的总个数

既然暴力解法效率不高,我们就需要优化思路。结合前面的核心结论——尾随零的数量=质因数5的个数,我们可以用数学公式直接计算,无需逐个遍历。

核心逻辑:

  • n/5:计算1到n中,至少包含1个5的数的个数(比如n=25,有5、10、15、20、25,共5个,25/5=5)。

  • n/25:计算1到n中,至少包含2个5的数的个数(比如25,包含2个5,25/25=1;50包含2个5,以此类推)——这些数在n/5中已经被统计过1次,这里需要再额外统计1次。

  • n/125:计算1到n中,至少包含3个5的数的个数(比如125,包含3个5),同样需要额外统计1次。

  • 以此类推,直到n除以5的某次幂结果为0,停止计算,累加所有结果,就是质因数5的总个数。

对应代码如下(TypeScript):

/**
 * n/5 = 有多少个数 至少含 1 个 5
 * n/25 = 有多少个数 额外多 1 个 5(25=5×5)
 * n/125 = 有多少个数 再额外多 1 个 5(125=5×5×5)
 * 以此类推……
 * @param n 
 * @returns 
 */
function trailingZeroes_2(n: number): number {
  let ans = 0;
  while (n !== 0) {
    n = Math.floor(n / 5);
    ans += n;
  }
  return ans;
};

代码拆解

  • while循环:每次将n除以5(向下取整),得到当前层级(5^1、5^2、5^3...)中包含5的数的个数。

  • ans累加:将每次除以5的结果累加到ans中,直到n变为0(此时再除以5结果为0,无需继续统计)。

举个例子:n=25

  • 第一次循环:n=25→Math.floor(25/5)=5,ans=5(统计5、10、15、20、25各1个5)。

  • 第二次循环:n=5→Math.floor(5/5)=1,ans=5+1=6(统计25额外的1个5)。

  • 第三次循环:n=1→Math.floor(1/5)=0,循环结束,返回6。

验证:25!的尾随零数量确实是6,和代码计算结果一致。

优缺点分析

优点:效率极高,时间复杂度是O(log₅n),即使n=10^9,循环也只需要执行不到10次(5^10=9765625,5^13=1220703125),完全不会超时。

缺点:逻辑需要稍微转个弯,需要理解「多次除以5」的本质,新手可能需要多琢磨一下。

四、两种解法对比&总结

解法时间复杂度空间复杂度核心思路适用场景
暴力枚举(trailingZeroes_1)O(n×log₅n)O(1)逐个统计每个数的质因数5个数n较小,新手理解思路
数学优化(trailingZeroes_2)O(log₅n)O(1)累加n/5、n/25、n/125...的结果n较大,追求高效解题

五、解题关键&拓展思考

解题关键

  1. 抓住核心:尾随零的数量由质因数5的个数决定,而非10的个数(因为2的数量足够多)。

  2. 优化思路:避免逐个遍历,利用数学规律,直接计算不同层级(5^1、5^2...)中5的个数,大幅提升效率。

拓展思考

这道题的本质是「质因数分解」的应用,类似的题目还有「统计阶乘中某个质因数的个数」,解题思路可以复用:统计n/k + n/k² + n/k³ + ... 直到结果为0。

比如,如果题目问n!中质因数2的个数,思路完全一样,只是把5换成2即可——但注意,此时2的数量会比5多,所以如果是求尾随零,还是要统计5的个数哦。

六、总结

LeetCode 172这道题,看似简单,却能很好地考察我们「从暴力到优化」的解题思维。新手可以先写暴力解法,理解核心逻辑;再琢磨数学优化思路,掌握高效解题的技巧。