问题1.给定一个整数N,那么N的阶乘N!末尾有多少个0呢?例如:N=10,N!=3628800,N!的末尾有两个0。
问题2.求N!的二进制表示中最低位1的位置。
问题3.丑数问题,把只包含因子2、3、5的数称作丑数。写一个函数,要求输入一个数值n(n>0),求从小到大的第n个丑数的值。
问题4.质数问题,要求写一个函数,输入数值n(n>0),求不小于n的质数的个数。(注意:问题3与问题4的问法其实在本质上是一样的。)
首先分析问题1和2,这两个个题目就是关于阶乘的,而对于这样的问题用质因数分解法比较好处理。将N!进行因式分解:N!=2^X*3^Y*5^Z*T。
问题1即是求N!的因式分解中Z的值,因为2*5=10,且X>=Z。
问题2即是求N!的因式分解中X的值。
首先给出一个求某数中因子t的指数的算法:
int indexOfT(int num,int t)
{
int count = 0;
int j;
for(int i=1;i<=num;i++)
{
j=i;
while(j%t==0)
{
count++;
j/=t;
}
}
return count;
}
上面两题都可以循环调用这个函数来求结果。但是这样效率不高,对于N!求因式分解的指数,有个公式,例如求解Z:Z=[N/5]+[N/5^2]+[N/5^3]…。
公式中[N/5]表示不大于N的数中5的倍数贡献一个5,,[N/5^2]表示不大于N的数中5^2的倍数再贡献一个5……代码如下:
count = 0;
while(n)
{
n/=5;
count +=n;
}
现在来看丑数问题。
我们可以循环求至第n个丑数来解决问题:
bool IsUgly(int number)
{
while(number % 2 == 0)
number /= 2;
while(number % 3 == 0)
number /= 3;
while(number % 5 == 0)
number /= 5;
return (number == 1) ? true : false;
}
int GetUglyNumber_Solution1(int index)
{
if(index <= 0)
return 0;
int number = 0;
int uglyFound = 0;
while(uglyFound < index)
{
++number;
if(IsUgly(number))
{
++uglyFound;
}
}
return number;
}
这个代码效率低下,这里可以运用空间换时间的方式降低算法的时间复杂度。但是光想到空间换时间还不足够。其中还有点技巧,分析丑数的定义就可以知道,丑数应该是另一个丑数乘以2、3、5的结果(除1外),因此我们可以创建一个数组按顺序存储丑数。然后继续向后生成不在数组中的最小丑数,这个丑数肯定是之前丑数乘以2、3、5的结果,而这个数可以用t2、t3、t5进行标记(当然标记是动态的)。代码如下:
int min(int a,int b)
{
return a<b?a:b;
}
int GetUglyNumber_Solution2(int index)
{
if (index<=0)
return 0;
int* ugly=new int[index];
int count=1;
ugly[0]=1;
int t2,t3,t5;
t2=t3=t5=0;
int a2,a3,a5,minNum;
while (index>count)
{
count++;
a2=2*ugly[t2];
a3=3*ugly[t3];
a5=5*ugly[t5];
minNum=min(a2,min(a3,a5));
ugly[count-1]=minNum;
if (a2<=minNum)
t2++;
if (a3<=minNum)
t3++;
if (a5<=minNum)
t5++;
}
int result=ugly[count-1];
delete [] ugly;
return result;
}
最后来看质数问题。
对于求n是否质数,仅需循环测试2<=i<=sqrt(n),是否能整除n。为了提高效率可以引入一个筛子,当然筛子的大小可以自定义,下面代码中定义的筛子大小为6:
bool isPrime(int number)
{
if(number<=0) return false;
int n=sqrt(number);
for (int i=2;i<=n;i++)
if (number%i==0) return false;
return true;
}
int NumOfPrime(int index)
{
if (index<0)
return -1;
if (index==0)
return 0;
int count=0;
int i;
if (index>6)
{
count = 4;
int shaizi[6]={0,1,1,1,0,1};
i=-1;
for (int j=7;j<=index;j++)
{
i++;
if (i==6)
i=0;
if (shaizi[i]==0&&isPrime(j))
count++;
}
}
else
{
for (i=1;i<=index;i++)
if (isPrime(i))
count++;
}
return count;
}
这个程序运用空间换时间的方式是:每次对于求取的结果保存下来,之后进行求取值的时候查找上次保存过的记录,这样可以迅速找到,并且如果求取的n比记录中最大的N大的化,也可以在N的基础上继续算其余的步骤。为了节省空间,可以保存一些特殊的点就可以了,然后程序会越跑越快。
但是这个想法,与一般的思路不一样,不是算法的设计了,而是一种思想,甚至可以说有点作弊的味道。
这里就不给出具体程序了。