欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

阶乘算法之一N! 末尾有多少个零 博客分类: java 服务 java阶乘算法面试效率 

程序员文章站 2024-03-24 20:25:04
...

                                                                                题:给定一个整数N,求出N!末尾有多少个零,比如N=10N!=362880010!末尾有两个零。

 

首先温固一下阶乘的相关知识!

阶乘(factorial)是基斯顿·卡曼(Christian Kramp, 1760 – 1826)于1808年发明的运算符号。阶乘,也是数学里的一种术语。
任何大于1的自然数n阶乘表示方法:n!=1×2×3×……×n 或n!= n×(n-1)! 
0!=1,注意(0的阶乘是存在的).
双阶乘:
    当n为奇数时表示不大于n的所有奇数的乘积 如:7!!=1×3×5×7
     当n为偶数时表示不大于n的所有偶数的乘积(除0外) 如:8!!=2×4×6×8
     小于0的整数-n的阶乘表示:
   (-n)!= 1 / (n+1)!
   双阶乘是怎么提出来的,是根据阶乘推导出来的吗?这点百思不解?
 
然后理一下本题的解题思路:

      N个自然数相乘,结尾0的个数,依赖有多少个10相乘(有两个10相乘,结尾0的个数就为2),10=2×5,则可以理解为结尾0的个数依赖因子中2的个数和5的个数,而对于连续的自然数来说,2出现的频率比5高的多,所以最终只需要计算出因子中5的个数,即为答案。

 

   把想法整理为JAVA代码,如下所示:

 解题思路一:分析阶乘因子中的每一个数,计算其包含5的个数,最后求总和

   

private int zeroNum(int n){
      int ret = 0;
      for(int i=1;i<=n;i++){
        int j=i;
        while(j%5 == 0){
           ret++;
           j/=5;
        }
      }
      return ret;
}

 

    时间复杂度为:Nlog5N     

 

解题思路二:

f(x)表示正整数x末尾所含有的“0”的个数,则有: 
      
0 < n < 5时,f(n!) = 0; 
      
n >= 5时,f(n!) = k + f(k!), 其中 k = n / 5(取整)

 

     下面对这个结论进行证明: 
    (1)
n < 5结论显然成立。 
    (2)
n >= 5时,令n5k * 5(k-1) * ... * 10 * 5 * a,其中 n = 5k + r (0 <= r <= 4)a是一个不含因子“5”的整数。 
    
对于序列5k, 5(k-1), ..., 10, 5中每一个数5i(1 <= i <= k),都含有因子“5”,并且在区间 [5(i-1), 5i] (1 <= i <= k)内存在偶数,也就是说,a中存在一个因子“2”5i相对应。即,这里的k个因子“5” n!末尾的k“0”一一对应。 

n5k * 5(k-1) * ... * 10 * 5 * a = (5k * k!) *a

 

f(x)表示正整数x末尾所含有的“0”的个数, g(x)表示正整数x的因式分解中因子“5”的个数,则利用上面的的结论有: 

 f(n!) = g(n!) = g(5k * k!* a) =g(5k * k!)= k + g(k!) = k + f(k!) 
所以,最终的计算公式为: 
    
0 < n < 5时,f(n!) = 0; 
    
n >= 5时,f(n!) = k + f(k!), 其中 k = n / 5(取整)。 

 

 

public int method02(int n){
      int ret = 0;
      if(n<5){
        return ret;
      }
      int k = n/5;
      return k + method02(k);
}

  时间复杂度为:log5

 

 解题思路三:

    乘积末尾的0的个数依赖于因子中的2的个数和5的个数。对于阶乘来说,每2个数字就至少有一个2的因子,所以2的因子是足够的。5的因子相对少些,至少连5个数才能保证一定出现一个。注意,这里连续5个书保证出现一个5的因子是指最少的情况。比如12345,这就只会出现一个。但是考虑 212223242525 = 5 * 5,所以如果乘以25那就能得到25的因子。依次会有35的因子

    所以n!5的个数的计算是:[n/5]+[n/(5*5)]+[n/(5*5*5)]+....

 

public int method03(int n){
       int ret = 0;
       int baseNum = 5;
       while (n >= baseNum)
       {
           ret += n/baseNum;
           baseNum *= 5;
       }
       return ret;
}
时间复杂度为:log5

 

       对于解法二和解法三,我这里写的时间复杂度都为log5N  ,但实际验证,解法三比解法二更高效,所以不知道是否时间复杂度写的有问题?高人求解!!!

 

后记(顿悟):

    算法三之所以优于算法二,因为算法二是用到递归算法,递归一系列的函数调用,而函数的调用开销是很大的,系统要为每次函数调用分配存储空间,并将调用点压栈予以记录。而在函数调用结束后,还要释放空间,弹栈恢复断点。所以说,函数调用不仅浪费空间,还浪费时间。所以算法三在时间复杂度和空间复杂度上是优于算法二的。

                                                                    2013.6.20

 

参考资料:

  http://blog.csdn.net/chn_cf/article/details/6541281

  http://www.chinaunix.net/old_jh/23/926848.html

  http://www.pureweber.com/article/recursive-power-4/

 《编程之美》