【算法拾遗】阶乘

转载请注明出处:http://blog.csdn.net/ns_code/article/details/28335353

前言

主要看两道有关阶乘的题目,从中能够看出一些规律来。

题目一

    N!末尾0的个数

找末尾0出现的个数,那我们就要找产生0的乘数,即哪些数相乘会得到10。我们须要对N。进行质因数分解。因为10 = 2*5,因此0的个数至于N!中2和5出现的的对数有关。而能被2整除的数出现的频率比能被5整除的数要多得多。因此我们找出N!中质因数5出现的个数,即为N!末尾0的个数。

方法一

最直接的方法。就是计算1-N中每一个数的因式分解中5的个数,然后相加。代码例如以下:

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

这样的方法的时间复杂度为O(n)。

方法二

方法一中对不含质因数5的数也进行了推断。时间复杂度较高。

另外一种方法要用到例如以下结论:

  N。中含有的质因数k的个数为:[N/k]+[N/k^2]+[N/k^3]+...(总会存在一个t,使得k^t>N,便有[N/k^t]=0)

这个公式的证明并不难,自己尝试去理解下,当中[N/k]等于1。2,3...N中能被k整除的数的个数。

用该方法写成的代码例如以下;

int FactorialNum0_2(int n)
{
	int count = 0;
	while(n)
	{
		count += n/5;
		n /= 5;
	}
	return count;
}

该方法的时间复杂度为O(log5n)(这里是以5为底n的对数)。

题目二

    N!

二进制表示中最低位1的位置

比方3!为6,二进制为1010。那么最低位的1的位置为2,这里最左边的位置从1開始计算。

方法一

我们如果最低位的1的位置为第k位。则N!=2^(k-1)+...+2^t+...+2^p+...,这里t,p等意味着后面的第t+1位。第p+1位也为1,且p>t>k。这样非常easy看出来,N!中质因数2的个数为k-1个,也就是说。最低位1的位置即为N!

中质因数2的个数加1,因此问题又转化为了求N!中质因数2的个数了,相同利用结论:

N。中含有的质因数k的个数为:[N/k]+[N/k^2]+[N/k^3]+...(总会存在一个t,使得k^t>N,便有[N/k^t]=0)

这样写出的代码例如以下:

int Lowest1(int n)
{
	int count = 0;
	while(n)
	{
		count += n/2;
		n /= 2;
	}
	return count + 1;
}

该方法时间复杂度为O(log2n)(这里是以2为底n的对数)。

方法二

用例如以下结论:N!

中含有质因数2的个数。等于N减去N的二进制表示中1的个数。关于怎样求N的二进制表示中1的个数,參考我的这篇博文;http://blog.csdn.net/ns_code/article/details/25425577,最快的方法的操作步骤仅仅与二进制中1的个数相等,因此这样的方法的时间复杂度更好一些。尽管方法一已经非常快了。这样的方法的时间复杂度为O(k),当中k为N。中1的个数。

这样的方法的代码不再给出。

时间: 2024-10-04 01:20:50

【算法拾遗】阶乘的相关文章

【算法拾遗】二分查找递归非递归实现

转载请注明出处:http://blog.csdn.net/ns_code/article/details/33747953 本篇博文没太多要说的,二分查找很简单,也是常见常考的查找算法,以下是递归非递归的实现. 非递归实现: /* 非递归实现,返回对应的序号 */ int BinarySearch(int *arr,int len,int key) { if(arr==NULL || len<1) return -1; int low = 0; int high = len-1; while(l

算法拾遗[4]&mdash;&mdash;STL用法

主要bb一下优先队列和字符串吧. 哦还有 bitset. 优先队列 定义很容易: priority_queue<int> pq; 内部是一个堆. 基本操作 pq.top() 取堆顶元素; (没有 front() 方法!) pq.push(x) 插入; pq.pop() 删除(删除堆顶); pq.empty() 判断是否为空. 自定义优先级 最大堆: priority_queue<int> pq; 最小堆: priority_queue< int, vector<int&

算法-计算阶乘n!末尾0的个数

算法逻辑转载自计算阶乘n!末尾0的个数: 问题描述    给定参数n(n为正整数),请计算n的阶乘n!末尾所含有"0"的个数.    例如,5!=120,其末尾所含有的"0"的个数为1:10!= 3628800,其末尾所含有的"0"的个数为2:20!= 2432902008176640000,其末尾所含有的"0"的个数为4. 计算公式    这里先给出其计算公式,后面给出推导过程.    令f(x)表示正整数x末尾所含有的&q

算法训练 阶乘

时间限制:1.0s   内存限制:512.0MB 问题描述 一个整数n的阶乘可以写成n!,它表示从1到n这n个整数的乘积.阶乘的增长速度非常快,例如,13!就已经比较大了,已经无法存放在一个整型变量中:而35!就更大了,它已经无法存放在一个浮点型变量中.因此,当n比较大时,去计算n!是非常困难的.幸运的是,在本题中,我们的任务不是去计算n!,而是去计算n!最右边的那个非0的数字是多少.例如,5! = 1*2*3*4*5 = 120,因此5!最右边的那个非0的数字是2.再如:7! = 5040,因

【算法拾遗(java描写叙述)】--- 选择排序(直接选择排序、堆排序)

选择排序的基本思想 每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,知道所有记录排序完毕.主要有两种选择排序方法:直接选择排序(或称简单选择排序)和堆排序. 直接选择排序 基本思想 第i趟排序開始时,当前有序区和无序区分别为R[1 -- i-1]和R[i -- n](1 <= i <= n-1),该趟排序则是从当前无序区中选出关键字最小的记录R[k],将它与无序区的第一个记录R[i]交换,使R[1 -- i]和R[i+1 -- n]分别变为新的有序区和新的无序区.

算法篇——阶乘的精确值

来源:<算法竞赛入门经典>例题5.2.2 题目:输入不超过1000的正整数n,输出n!=1*2*3*…*n的精确结果. 样例输入:30 样例输出:265252859812191058636308480000000 分析:为了保存结果,需要分析1000!有多大.用计算器算一算不难知道,1000!约等于4*102567,因此可以用一个3000个元素的数组buf保存.为了方便起见,我们让f[0]保存结果的个位,f[1]是十位,f[2]是百位……(为什么要从低位逆序表示呢?因为如果从低位顺序表示,一旦

数据结构与算法—递归(阶乘、斐波那契、汉诺塔)

目录 递归介绍 递归求阶乘 递归求斐波那契 递归解决汉诺塔 总结 递归介绍 递归:就是函数自己调用自己. 子问题须与原始问题为同样的事,或者更为简单:递归通常可以简单的处理子问题,但是不一定是最好的.对于递归要分清以下概念: 自己调用自己 递归通常不在意具体操作,只关心初始条件和上下层的变化关系. 递归函数需要有临界停止点,即递归不能无限制的执行下去.通常这个点为必须经过的一个数. 递归通常能被其他方案替代(栈.数组正向求). 认识递归,递归函数通常简易但是对于初学者可能很难取理解它.拿一个递归

【算法拾遗(java描述)】--- 插入排序(直接插入排序、希尔排序)

插入排序基本思想 每次将一个待排序的记录按其关键字大小插入到前面已经拍好序的子文件的适当位置,直到全部记录插入完成为止. 直接插入排序 基本思想 直接插入排序的基本操作是将一个记录插入到已排好序的有序表中,从而得到一个新的有序表.即假设待排序的记录存放在数组R[1······n]中,排序过程中,R被分成两个子区间R[1······i]和R[i+1······n],其中,R[1······i]是已经排好序的有序区:R[i+1······n]是当前未排序的部分.将当前无序区的第一个记录R[i+1]插

【算法拾遗(java描写叙述)】--- 插入排序(直接插入排序、希尔排序)

插入排序基本思想 每次将一个待排序的记录按其keyword大小插入到前面已经拍好序的子文件的适当位置,直到全部记录插入完毕为止. 直接插入排序 基本思想 直接插入排序的基本操作是将一个记录插入到已排好序的有序表中.从而得到一个新的有序表.即如果待排序的记录存放在数组R[1······n]中,排序过程中,R被分成两个子区间R[1······i]和R[i+1······n],当中.R[1······i]是已经排好序的有序区:R[i+1······n]是当前未排序的部分. 将当前无序区的第一个记录R[