求n的阶乘的算法框图_阶乘相关的算法题,东哥又整活儿了

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

83ba5edd17db6fee924f908af4d73c35.png

扫码关注添加客服

进Python社群▲

0a4c29e6050130f86888572cfc42072b.png

扫码关注添加客服

进Java社群

作者丨labuladong 

来源丨labuladong 

读完本文,你可以去 LeetCode 上拿下如下题目:

172、阶乘后的零(难度 Easy)

793、阶乘后 K 个零(难度 Hard)

笔试题中经常看到阶乘相关的题目,今天说两个最常见的题目:

1、输入一个非负整数n,请你计算阶乘n!的结果末尾有几个 0

比如说输入n = 5,算法返回 1,因为5! = 120,末尾有一个 0。

函数签名如下:

int trailingZeroes(int n);

2、输入一个非负整数K,请你计算有多少个n,满足n!的结果末尾恰好有K个 0

比如说输入K = 1,算法返回 5,因为5!,6!,7!,8!,9!这 5 个阶乘的结果最后只有一个 0,即有 5 个n满足条件。

函数签名如下:

int preimageSizeFZF(int K);

我把这两个题放在一起,肯定是因为它们有共性,可以连环击破。下面我们来逐一分析。

题目一

肯定不可能真去把n!的结果算出来,阶乘增长可是比指数增长都恐怖,趁早死了这条心吧。

那么,结果的末尾的 0 从哪里来的?我们有没有投机取巧的方法计算出来?

首先,两个数相乘结果末尾有 0,一定是因为两个数中有因子 2 和 5,因为 10 = 2 x 5。

也就是说,问题转化为:n!最多可以分解出多少个因子 2 和 5

比如说n = 25,那么25!最多可以分解出几个 2 和 5 相乘?这个主要取决于能分解出几个因子 5,因为每个偶数都能分解出因子 2,因子 2 肯定比因子 5 多得多。

25!中 5 可以提供一个,10 可以提供一个,15 可以提供一个,20 可以提供一个,25 可以提供两个,总共有 6 个因子 5,所以25!的结果末尾就有 6 个 0。

现在,问题转化为:n!最多可以分解出多少个因子 5

难点在于像 25,50,125 这样的数,可以提供不止一个因子 5,怎么才能不漏掉呢?

这样,我们假设n = 125,来算一算125!的结果末尾有几个 0:

首先,125 / 5 = 25,这一步就是计算有多少个像 5,15,20,25 这些 5 的倍数,它们一定可以提供一个因子 5。

但是,这些足够吗?刚才说了,像 25,50,75 这些 25 的倍数,可以提供两个因子 5,那么我们再计算出125!中有 125 / 25 = 5 个 25 的倍数,它们每人可以额外再提供一个因子 5。

够了吗?我们发现 125 = 5 x 5 x 5,像 125,250 这些 125 的倍数,可以提供 3 个因子 5,那么我们还得再计算出125!中有 125 / 125 = 1 个 125 的倍数,它还可以额外再提供一个因子 5。

这下应该够了,125!最多可以分解出 20 + 5 + 1 = 26 个因子 5,也就是说阶乘结果的末尾有 26 个 0。

理解了这个思路,就可以理解解法代码了:

int trailingZeroes(int n) {

这里divisor变量使用 long 型,因为假如n比较大,考虑 while 循环的结束条件,divisor可能出现整型溢出。

上述代码可以改写地更简单一些:

int trailingZeroes(int n) {

这样,这道题就解决了,时间复杂度是底数为 5 的对数级,也就是O(logN),我们看看下如何基于这道题的解法完成下一道题目。

题目二

现在是给你一个非负整数K,问你有多少个n,使得n!结果末尾有K个 0。

一个直观地暴力解法就是穷举呗,因为随着n的增加,n!肯定是递增的,trailingZeroes(n!)肯定也是递增的,伪码逻辑如下:

int res = 

前文 二分搜索只能用来查找元素吗? 说过,对于这种具有单调性的函数,用 for 循环遍历,可以用二分查找进行降维打击,对吧?

搜索有多少个n满足trailingZeroes(n) == K,其实就是在问,满足条件的n最小是多少,最大是多少,最大值和最小值一减,就可以算出来有多少个n满足条件了。

那不就是二分查找「搜索左侧边界」和「搜索右侧边界」这两个事儿嘛?

先不急写代码,因为二分查找需要给一个搜索区间,也就是上界和下界,上述伪码中n的下界显然是 0,但上界是+inf,这个正无穷应该如何表示出来呢?

首先,数学上的正无穷肯定是无法编程表示出来的,我们一般的方法是用一个非常大的值,大到这个值一定不会被取到。比如说 int 类型的最大值INT_MAX(2^31 - 1,大约 31 亿),还不够的话就 long 类型的最大值LONG_MAX(2^63 - 1,这个值就大到离谱了)。

那么我怎么知道需要多大才能「一定不会被取到」呢?这就需要认真读题,看看题目给的数据范围有多大

这道题目实际上给了限制,K是在[0,10^9]区间内的整数,也就是说,trailingZeroes(n)的结果最多可能达到10^9

然后我们可以反推,当trailingZeroes(n)结果为10^9时,n为多少?这个不需要你精确计算出来,你只要找到一个数hi,使得trailingZeroes(hi)10^9大,就可以把hi当做正无穷,作为搜索区间的上界。

刚才说了,trailingZeroes函数是单调函数,那我们就可以猜,先算一下trailingZeroes(INT_MAX)的结果,比10^9小一些,那再用LONG_MAX算一下,远超10^9了,所以LONG_MAX可以作为搜索的上界。

注意为了避免整型溢出的问题,trailingZeroes函数需要把所有数据类型改成 long

// 逻辑不变,数据类型全部改成 long

现在就明确了问题:

n属于区间[0,LONG_MAX]我们要寻找满足trailingZeroes(n) == K的左侧边界和右侧边界

根据前文 二分查找算法框架,可以直接把搜索左侧边界和右侧边界的框架 copy 过来:

/* 主函数 */

现在,这道题基本上就解决了,我们来分析一下它的时间复杂度吧。

时间复杂度主要是二分搜索,从数值上来说LONG_MAX是 2^63 - 1,大得离谱,但是二分搜索是对数级的复杂度,log(LONG_MAX) 是一个常数 63;每次二分的时候都会调用一次trailingZeroes(n)函数,它的复杂度 O(logN)。

那么算法的复杂度就是 O(logN) 吗?其实你会发现 trailingZeroes 函数传入的参数 n 也是在区间[0,LONG_MAX]之内的,所以我们认为这个 O(logN) 最多也不过 63,所以说可以认为时间复杂度为 O(1)。

综上,由于我们根据 K 的大小限制了数据范围,用大 O 表示法来说,整个算法的时间复杂度为 O(1)。

当然,如果说不考虑数据范围,这个算法的复杂度应该是 O(logN*logN),也就是二分查找的复杂度乘 trailingZeroes 的复杂度。

d90098b7f5842413982cb7745bfaefe0.gif
程序员专栏 扫码关注填加客服 长按识别下方二维码进群

53325d0f05f3d5c4fcad965c37c8e3d9.png

近期精彩内容推荐:  

aba02d7d9932e35675e2cfa2f7fb6598.png 《最受欢迎的女友职业排行榜Top10》

aba02d7d9932e35675e2cfa2f7fb6598.png 在一个公司死磕了5-10年的人,都怎么样了?

aba02d7d9932e35675e2cfa2f7fb6598.png 面试官:你知道java类是怎么跑起来的吗?

aba02d7d9932e35675e2cfa2f7fb6598.png Python时间模块,超实用总结!

ee225c0f85fff38cb0024c502c39a823.png

26f018278d5f237a9efb0ef068321729.png

在看点这里4c25f416c36da3d79f320543c97e38ee.gif好文分享给更多人↓↓

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值