Leetcode历程 -- 172. n阶乘后的 0 个数

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值