• [数字]整数数字的算法


    计算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 == '')
    		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
    

      

  • 相关阅读:
    关于websocket
    Convert word or html to wiki syntax
    How to center an image?
    Multiline strings in JavaScript
    JavaScript tips and tricks 4
    Use IE userdata behavior as a clientside data storage
    Confused with JavaScript prototype
    Javascript中的作用域(scope)
    JavaScript tips and tricks 2
    AOP in JavaScript
  • 原文地址:https://www.cnblogs.com/stemon/p/4759416.html
Copyright © 2020-2023  润新知