阶乘问题给你一个整数N,希望求得N!的末尾有几个0。10!=3628800,末尾有2个0. 如何解答?很简单嘛,求其阶乘,然后对10求模就可以了啊。是吗?如果是1000的阶乘呢?会不会溢出呢?这确实是一个问题,那如何解答呢?好好想一想。 末尾的0和谁有关系?当然是和10 。那10又和谁有关系?10=2*5 。而N!=1*2*3**N; 那么,两者有什么关系吗?当然。先对N进行质因数分解得到N!=2^x*3^y*5^z*... 如果,如果我们知道了x和z的值,不就是知道了10的个数了吗?那也就求得问题的答案了。可是我们没必要求x,因为2和5出现的频率不一样,而且要同时出现一个2和一个5才会得到10 。所以我们只要得到Z的值,就知道了答案。求Z也就是求N!中几个5,又绕回去了?NO,我们这次不求阶乘的值,求每一个数(1--N)中含有的因子5的个数,加在一起就是总数。 OK,编码实现一下
int LastZeroNum(int n)
{
int num=0;
for(int i=1;i<=n;i++)
{
for(int j=i;j%5==0;j/=5)
num++;
}
return num;
}
时间复杂度O(n^2),不是很好啦。因为我们做了很多的无用功,比如1%5=1,2%5=2...5%5=0,6%5=1. 。
有些数压根就不是5的倍数,我们没必要遍历他们,我们只需遍历5的倍数即可。
编码实现:
int LastZeroNum(int n)
{
int num=0;
for(int i=5;i<=n;i+=5)
{
for(int j=5;i%j==0;j*=5) //求因子5的个数
num++;
}
return num;
}
很nice的改进。这样遍历就少很多了。
但是,时间复杂度T(n)=O(n^2)没有改变。
可以继续改进。
刚才的算法所求的也就是5 5*2 5*3 .... 5*(n/5) 中5的个数。n=15时,也就是求 5 \10 \15中5的个数。可不可以化简一下?
如果是5的倍数每个数至少含有1个5,如果是25的倍数每个数至少含有2个5,如果是125的倍数每个数至少含有3个5。可以分步求出:5的倍数贡献一个,25的倍数再贡献一个,125的倍数再贡献一个。加在一起就是总和。也就是公式Z=[n/5]+[n/5^2]+…,最后一个肯定是0。 确实好了很多啊。
这个代码可以写的再简洁一些。
编码实现:
int LastZeroNum(int n)
{
int num=0;
for(int i=n;i>0;i/=5)
{
num+=i/5;
}
return num;
}
复杂度又降低了,这次是时间复杂度T(n)=O(log5n)=O(lgn)。非常漂亮。 这个时候如果问你N!的二进制表示中最低位1的位置,你如何回答?N=3,N!=6=(0110) 想一想,二进制的末尾有0代表什么,代表是偶数,代表了能被2整除。如果有2个0,那么只要两次整除就好了,当然还要加1.因为不是求末尾0的个数,而是求1的位置。也就是说这问题要求的就是N!中质因数2的个数+1啦。质因数2的个数,同样的道理用同样的方法可以求得。 编码实现:换一种实现方式
int LowestOne(int n)
{
int num=0;
while(n)
{
n>>=1;
num+=n;
}
return num+1;
}
其实还有一种方法可以求得这个问题,很巧妙。N!中质因数2的个数就等于N--N的二进制表示中1的个数。利用二进制的运算来证明。假设N=10,表示成二进制就变成了1010.
用上一种方法举例证明 num=N/2+N/2^2+...=101+10+1(用二进制的表示来进行运算)
则 101+10+1=100+1+10+1=111+1=(1000-1)+(10-1)=1010(10)--N(10)二进制中1的个数=10-2=8。
再举个例子
N=11011=1101+110+11+1=(1000+100+1)+(100+10)+(10+1)+1=1111+111+1
N=(10000-1)+(1000-1)+(10-1)+(1-1)=11011(27)-N(27)中1的个数。
很nice的方法,只能这么证明了。位操作已经很好的说明了如何求二进制中1的个数。所以这个编码就非常简单了。略。
转载请注明出处http://blog.csdn.net/sustliangbo/article/details/9289311