题目
lintCode题目:设计一个计算n阶乘中尾部0的个数。
我的算法
对于这道题目我的思路就是把阶乘结果计算出来,然后去算尾部0的个数。
public long trailingZeros(long n) {
// write your code here, try to do it without arithmetic operators.
long factorialResult = factorialRecursive(n);
System.out.println(factorialResult);
long count = 0;
if(factorialResult == -1 || factorialResult == 1){
return count;
}
while(factorialResult%10 == 0){
count++;
factorialResult = factorialResult/10;
}
return count;
}
public long factorialRecursive(long n){
if(n < 0){
return -1;
}
if(n == 0){
return 1;
}
if(n < 2){
return n*1;
}
// System.out.println(n);
return n*factorialRecursive(n-1);
}
这是我的第二道算法题,可以说我完全没有想到要用数学的思路去处理它,只想着按照题目描述的套路走,我的这种解法是需要计算很大量的数据的,并且这种递归的方式计算阶乘在位数太大的时候是不可行的。
算法漏洞
我试了一下这种方式long类型的n最大只能是20,66及以上的话结果都是0,23-65的数结果就不正确。看了下说int类型最大就12,34及以上就输出0。
找了下资料发现有一种可以存更大的数的阶乘计算方式,使用math包下面的big的类型就可以存储更大的数。这里有两个可选类BigInteger和BigDecimal。
在这里有个引伸的知识点,通常我们说的阶乘都是指整数的阶乘,其实小数也是可以进行阶乘的,不过这就涉及到伽玛函数,我对这个不是很懂,就此打住没有去深入了解了。
我选择了类BigInteger,使用其valueOf方法,将原来的long类型数转成大数存储这样其他我还能计算很很很多个阶乘了。但是这样去计算大数类型的时候是不能直接使用符号的+-*/%这类方法的,只能使用BigInteger提供的方法add(加)、subtract(减)、multiply(乘)、divide(整除)、remainder(求余)。
public BigInteger factorialRecursive(long n){
if(n < 0){
return BigInteger.valueOf(-1);
}
if(n == 0){
return BigInteger.valueOf(1);
}
if(n < 2){
return BigInteger.valueOf(n).multiply(BigInteger.valueOf(1));
}
// System.out.println(n);
return BigInteger.valueOf(n).multiply(factorialRecursive(n-1));
}
然而...当你的n到了八千多的时候java它就报错了啊java.lang.StackOverflowError。所以这样看来为了算一个阶乘尾部0个数去把阶乘结果算出来是不合理的。
最优算法
嗯我又去找了下最优解,发现原来这是个数学题......
1.首先考虑下当需要阶乘的时候,将阶乘的每个数拆成最小因子;
2.这道题是需要尾部0的个数,那什么能组成一个尾部的0呢,2*5产生的;
3.一个阶乘里面的数,5的数量比2的数量要小(毕竟5比2大那么多,2出现两次5才出现一次呢);
4.所以只需要计算5这个素因子的出现次数就行啦。
public long trailingZeros(long n) {
long count = 0;
if(n <= 0){
return -1;
}
while(n != 0){
count += n/5;
n /= 5;
}
return count;
}
总结:
说实话,我想了挺久没想明白为啥这个是求尾部0的个数,而不是所有0的个数......然后发现自己是真傻,2*5会产生一个尾部的0,那这个尾部的0无论是乘谁都有个尾部0,同时再乘一个尾部0就会多增加一个尾部0,意味着只要有2*5就会产生一个0。
还是需要多多锻炼自己的思维的。毕竟不用会生锈......