题目描述
这道题的题目名称非常的难懂,但是读了题目内容以后,就不难理解了,定义函数f(x)为x!的末尾0的个数,现在给了我们一个非负整数K,问使得f(x)=K成立的非负整数的个数。
问题分析
之前做过一道有关阶乘末尾零的个数的题,从那道里知道了末尾0其实是由2和5相乘为10得到的,而阶乘中2的数量远多于5的个数,所以10的个数就只取决于5的个数。
需要注意的一点就是,像25,125,这样的不只含有一个5的数字需要考虑进去。比如,24的阶乘末尾有4个0,分别是5,10,15,20中的四个5组成的,而25的阶乘末尾就有6个0,分别是5,10,15,20中的各一个5,还有25中的两个5,所以共有六个5,那么就不存在其阶乘数末尾有5个0的数。
除此之外,我们知道20,21,22,23,24,这五个数的阶乘数末尾零的个数其实是相同的,都是有4个,因为它们包含的5的个数相同。而19,18,17,16,15,这五个数末尾零个数相同,均为3。那么我们其实可以发现,每五个数,必会至少多出1个5,有可能更多。所以阶乘末尾零个数均为K个的x值,只有两种情况,要么是5,要么是0。
算法设计
算法1
基于之前那道题的解法,我们有了如何快速求一个给定的数字阶乘末尾零的个数,那么我们只要找到了一个这样的数,其阶乘末尾零的个数等于K的话,那么就说明总共有5个这样的数,返回5,反之,如果找不到这样的数字,就返回0。那么像这种选一个candidate数字,再用二分搜索进行验证的操作。首先要确定二分搜索法的范围,左边界为0,关键是来确定右边界。
我们来分析一下,一个数字的阶乘末尾零个数为K,那么这个数字能有多大,就拿前面举的例子来说吧,末尾有4个0的最大数字是24,有六个0的最大是29,那么我们发现它们都不会超过5*(K+1)这个范围,所以这就是我们的右边界。然后进行二分搜索法。
注意:右边界可能会超过整型数范围,所以要用长整型来表示。
class Solution {
public:
int preimageSizeFZF(int K) {
long left = 0, right = 5L * (K + 1);
while (left < right) {
long mid = left + (right - left) / 2;
long cnt = numOfTrailingZeros(mid);
if (cnt == K) return 5;
else if (cnt < K) left = mid + 1;
else right = mid;
}
return 0;
}
long numOfTrailingZeros(long x) {
long res = 0;
for (; x > 0; x /= 5) {
res += x / 5;
}
return res;
}
};
算法2
在算法1 的基础上进行代码模块整合,将while循环柔和进去即可,代码减少了30%。
class Solution {
public:
int preimageSizeFZF(int K) {
long left = 0, right = 5L * (K + 1);
while (left < right) {
long mid = left + (right - left) / 2, cnt = 0;
for (long i = 5; mid / i > 0; i *= 5) {
cnt += mid / i;
}
if (cnt == K) return 5;
else if (cnt < K) left = mid + 1;
else right = mid;
}
return 0;
}
};
算法3
通过后续的资料查询,我发现有博主通过规律寻找的方法,找出了其中蕴含的规律,顺利解决该问题。不过我以为,这样的解法不具有算法设计的巧妙性,有一点运气的程序在里面。所以,我在这里不做赘述,截图供参考,丰富思维,同时,我也按照他的思路实现了他的算法。
class Solution {
public:
int preimageSizeFZF(int K) {
if (K < 5) return 5;
int base = 1;
while (base * 5 + 1 <= K) {
base = base * 5 + 1;
}
if (K / base == 5) return 0;
return preimageSizeFZF(K % base);
}
};