计算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