[数字]整数数字的算法

计算n的阶乘末尾0的个数

首先,算出n的阶乘的结果再去计算末尾有多少个0这种方法是不可取的, 因为n的阶乘是一个非常大的数,分分种就会溢出。我们应当去分析, 是什么使n的阶乘结果末尾出现0。

n阶乘末尾的0来自因子5和2相乘,5*2=10。因此,我们只需要计算n的阶乘里, 有多少对5和2。注意到2出现的频率比5多,因此,我们只需要计算有多少个因子5即可。

举一个例子:

16!= 16*15*14*13*12*11*10*9*8*7*6*5*4*3*2*1;
现在要计算的就是[1...16]这几个数字中,有多少个因子5.
5,10,15都是一个。
有一个好的办法就是对这个[1...16]的区间分段,每5个一组。
[1...5][6...10][11...15][16]可以看到每组有一个5.
所以n/5就可以得到分组的组数。也就得到了5的个数。

但是还有一个问题:
25!=25*24*...*1;
分组之后[1...5][6...10][11...15][16...20][21...25]可以看到25中有两个5.
所以可以看出,每连续5组就会多出一个5.

总结:把[1...n]中的元素,每5个一组,得到的组数就是第一轮得到的5的个数。
	然后每5个组合并成更大的组,没得到一个大的组就又得到一个因子。
	知道最后剩下的组数小于5,不够再合并成一个更大的组。

代码实现:

long long trailingZeros(long long n) 
{
	long long cnt = 0;
	while(n /= 5)
	{
		cnt += n;
	}
	
	return cnt;
}

  注意返回类型为long long,因为有的时候阶乘很大,后面的0很多,多到超出了int的表示最大值。

总结:这个题目的核心点在求一个连乘多项式的某个因子的个数。

1到n的数字中数字1出现的次数统计

对于这个题目,可以用暴力算法来解决问题

int NumberOf1Between1AndN(int n)
{
	int res = 0;
	for(int i = 1; i <= n; i++)
	{
		res += NumberOf1(i);
	}
	
	return res;
}

int NumberOf1(int n)
{
	int cnt = 0;
	while(n != 0)
	{
		if(n % 10 == 1)
		{
			cnt++;
		}
		n = n / 10;
	}
	return cnt;
}

  这种方法简单暴力,但是时间复杂度很高。


 

再看另外的更好的解法,先来做一个概率统计的题目:

计算0000~9999之间所有的数字中1出现的次数的和。

这个题目就回到了正规的题目中来了,无非就是在四位数字中,固定一个是1,其余的三位,每一位都有0~9的10中可能。然后在一次固定其他的几位为1。

所以最后的结果是:4*103

但是现在题目稍微修改一下:

计算3246~13245之间所有的数字中1出现的次数的和。

这个题目其实就是上面题目的一个小的变化,最高位为1的数共有10000~12345,一共2346个数字,也就是2346个1。算完了最高位的1,现在看看非最高位的1的个数,3246~13245这些数字在非最高位的变化同0000~9999的变化是一样的,无非就是分成了两段3246~9999和0000~3245。所以两个题目的解法是一样的。


 

现在重新看这个让计算1~n中1出现次数的题目。再举一个例子,1~21345我们可以把这个问题分成两部分1~1345和1346~21345。对于后者先统计最高位的1出现的次数,也就是10000~19999一共10000个。然后计算非最高位的1出现的次数,分成两段1346~11345和11346~21345,这两段其实就是上面的那个概率题的求解问题。

所以算法设计:

拿1~21345这个数举例子,首先要将int类型的数字转换成字符串。
1.将数字分成两部分,1~1345和1346~21345。
2.计算1346~22345这个数字段,先统计最高位的1出现的次数。
3.在统计非最高位1出现的次数。继续分段,弄成可以求解的0000~9999这样的小段。
4.对1~1345这段数字递归调用。

递归调用的出口:
当逐步的分段,最后到1位数字的时候,就能直接返回1的个数是0或者1。

代码实现:

int NumberOf1Between1AndN_Solution(int n)
{
	char str[100];
	if(n <= 0)
		return 0;
	sprintf(str, "%d", n);
	
	return NumberOf1(str);
}

int NumberOf1(const char *str)
{
	if(str == NULL || *str < '0' || *str > '9' || *str == '\0')
		return 0;
	
	int length = strlen(str);
	int first = *str - '0';
	
	//递归函数的出口
	if(length == 1 && first > 0)
		return 1;
	if(length == 1 && first == 0)
		return 0;
	
	//计算最高位的1的个数
	int numHighDigit = 0;
	if(first == 1)
		numHighDigit = atoi(str + 1) + 1;
	else if(first > 1)
		numHighDigit = pow(10.0, length - 1);
	
	//计算非最高位的个数
	int numLowDigit = 0;
	numLowDigit = first * (length - 1) * pow(10.0, length - 2);
	
	//递归调用另外的一段数字中1的个数
	int otherNumDigit = 0;
	otherNumDigit = NumberOf1(str + 1);
	
	return numHighDigit + numLowDigit + otherNumDigit;
}

边界测试用例:

测试用例:
100
102
000
40
111

  

posted @ 2015-08-26 09:45  stemon  阅读(782)  评论(0编辑  收藏  举报