Leetcode历程 – 172. n阶乘后的 0 个数
题目描述
首先我们看一下题目的描述
给定一个整数 n,返回 n! 结果尾数中零的数量。
示例 1:
- 输入: 3
- 输出: 0
- 解释: 3! = 6, 尾数中没有零。
示例 2:
- 输入: 5
- 输出: 1
- 解释: 5! = 120, 尾数中有 1 个零.
- 说明: 你算法的时间复杂度应为 O(log n) 。
如果不考虑题目末尾的时间复杂度为O(log n),则我们的思考过程是怎么样的呢?
题解
1. 算出阶乘 n! , 然后求一下结果中的0的个数。代码如下:
/**
* 采用先求阶乘后算0 的方式
*/
public static int trailingZeroes(int n) {
int countZero = 0;
for(int i = 1; i <= n; i++) {
countZero += countFive(i);
}
return countZero;
}
private static int factorial(int n) {
if(n == 0 || n == 1)
return 1;
else
return factorial(n -1) * n;
}
但是考虑到阶乘的 O(x^N) 的指数级复杂度,此法不可行。
2. 那我们再考虑下题目描述,求阶乘后的 0 的个数,末尾的 0 是由什么产生的呢?
由 2 * 5 = 10 , 我们可以将阶乘中的每个数是否包含因子 2 和 5 ,包含几个 2 和 5 求出来。以此来求出结果中包含几个 0 ,且由于数字增加的过程可知,当增长到含 5 时, 2 的个数明显是多余 5 的。故而,只需要判断每一个因子是否包含 5 即可。
可编写代码如下:
public static int trailingZeroes(int n) {
int countZero = 0;
for(int i = 1; i <= n; i++) {
countZero += countFive(i);
}
return countZero;
}
private static int countFive(int i) {
int countFive = 0;
while(i % 5 == 0) {
countFive ++;
i /= 5;
}
return countFive;
}
我们对此种算法的时间复杂度进行分析:
在trailingZeroes方法,可以知道,我们对 n 进行了遍历,算法的复杂度为 O(n) , 在方法countFive中,时间复杂度为 ***O(log n)***, 因此,此种算法的时间复杂度为 ***O(nlog n)***。 在test cases 中通过的情况为500/502 , 还有两个测试用例超出了时间限制,所以我们继续优化算法。
3. 考虑到每两个包含 5 的因子中间的数都是不包含 5 的,所以可以将增加的步长变为 5。
代码如下:
public static int trailingZeroes(int n) {
int countZero = 0;
int spare = n % 5;
for(int i = 5; i <= (n - spare); i = i+5) {
countZero += countFive(i);
}
return countZero;
}
private static int countFive(int i) {
int countFive = 0;
while(i % 5 == 0) {
countFive ++;
i /= 5;
}
return countFive;
}
同样地,我们对算法的时间复杂度进行分析:
在trailingZeroes()方法,可以知道,我们对 n 进行了遍历,算法的复杂度为 O(n)/5 , 在方法countFive()中,时间复杂度为 ***O(log n)***, 因此,此种算法的时间复杂度为 ***O(nlog n)***。
但是,相对于上面一种方法,在trailingZeroes() 中我们将时间复杂度变为原来的 1/5.
但是,我们仍然未能达到题目中的时间复杂度***O(log n)***的要求.在测试中,test cases 通过的情况为 501/502 ,通过查看结果,只有Integer.MAX_VALUE 未通过测试。所以,我们继续优化我们的算法.
4. 将解法的时间复杂度压缩在 O(log n)
求出最接近 n 的5的 n 次方的数是多少;即为 log n / log 5 ,然后计算然后依次计算n中包含多少个51、52……5^c,累加即可。
int count = (int)Math.log(n)/Math.log(5);
代码如下:
public int trailingZeroes(int n) {
int c = (int)(Math.log(n) / Math.log(5));
int ans = 0;
for (int i = 1; i <= c; i++) {
int d = (int)(Math.pow(5,i));
ans += n / d;
};
return ans;
}
6. 可简化如下为 n/5 + n /25 + n/125 + …
编码如下:
public int trailingZeroes(int n){
int total = 0;
while (n >= 5) {
n = (int)Math.floor(n / 5);
total += n;
}
return total;
}
到此,此题解决。
总结
在做这个题的过程中,我对题目的理解逐渐加深。在算法的时间复杂度上,一步步优化,最终达到题目的要求。虽然是一个 simple 类的题目,但在解题的过程中,逐渐地打通思路。慢慢梳理数字的特征,使我受益良多也体会到了解题的愉悦。
在前行路上继续加油吧!
kejin–2019/12/6