阶乘相关的算法题,东哥又整活儿了
学算法认准 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) {int res = 0;long divisor = 5;while (divisor <= n) {res += n / divisor;divisor *= 5;}return res;
}
这里divisor
变量使用 long 型,因为假如n
比较大,考虑 while 循环的结束条件,divisor
可能出现整型溢出。
上述代码可以改写地更简单一些:
int trailingZeroes(int n) {int res = 0;for (int d = n; d / 5 > 0; d = d / 5) {res += d / 5;}return res;
}
这样,这道题就解决了,时间复杂度是底数为 5 的对数级,也就是O(logN)
,我们看看下如何基于这道题的解法完成下一道题目。
题目二
现在是给你一个非负整数K
,问你有多少个n
,使得n!
结果末尾有K
个 0。
一个直观地暴力解法就是穷举呗,因为随着n
的增加,n!
肯定是递增的,trailingZeroes(n!)
肯定也是递增的,伪码逻辑如下:
int res = 0;
for (int n = 0; n < +inf; n++) {if (trailingZeroes(n) < K) {continue;}if (trailingZeroes(n) > K) {break;}if (trailingZeroes(n) == K) {res++;}
}
return 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
long trailingZeroes(long n) {long res = 0;for (long d = n; d / 5 > 0; d = d / 5) {res += d / 5;}return res;
}
现在就明确了问题:
n
属于区间[0,LONG_MAX]
,我们要寻找满足trailingZeroes(n) == K
的左侧边界和右侧边界。
根据前文 二分查找算法框架,可以直接把搜索左侧边界和右侧边界的框架 copy 过来:
/* 主函数 */
int preimageSizeFZF(int K) {// 左边界和右边界之差 + 1 就是答案return right_bound(K) - left_bound(K) + 1;
}/* 搜索 trailingZeroes(n) == K 的左侧边界 */
long left_bound(int target) {long lo = 0, hi = LONG_MAX;while (lo < hi) {long mid = lo + (hi - lo) / 2;if (trailingZeroes(mid) < target) {lo = mid + 1;} else if (trailingZeroes(mid) > target) {hi = mid;} else {hi = mid;}}return lo;
}/* 搜索 trailingZeroes(n) == K 的右侧边界 */
long right_bound(int target) {long lo = 0, hi = LONG_MAX;while (lo < hi) {long mid = lo + (hi - lo) / 2;if (trailingZeroes(mid) < target) {lo = mid + 1;} else if (trailingZeroes(mid) > target) {hi = mid;} else {lo = mid + 1;}}return lo - 1;
}
如果对二分查找的框架有任何疑问,建议好好复习一下前文 二分查找算法框架,这里就不展开了。
现在,这道题基本上就解决了,我们来分析一下它的时间复杂度吧。
时间复杂度主要是二分搜索,从数值上来说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 的复杂度。
往期推荐 ????
二叉树的题,就那几个框架,枯燥至极????
状态压缩技巧:动态规划的降维打击
一个函数秒杀 2Sum 3Sum 4Sum 问题
回溯算法和动态规划,到底谁是谁爹
东哥手写正则通配符算法,结构清晰,包教包会!
_____________
学好算法全靠套路,认准 labuladong 就够了。
知乎、B站搜索:labuladong
《labuladong的算法小抄》即将出版,公众号后台回复关键词「pdf」下载,回复「进群」可加入刷题群。
阶乘相关的算法题,东哥又整活儿了相关推荐
- 链表相关的算法题大汇总 — 数据结构之链表奇思妙想
http://blog.csdn.net/lanxuezaipiao/article/details/22100021 基本函数(具体代码实现见后面) 1,构造节点 //定义节点类型 struct N ...
- java算法题——豪哥打牌
豪哥打牌 题目以及要求: 样例 题目以及要求: 题目描述 著名ACM选手豪哥退役后迷上了打扑克.对对碰是一种十分有趣的双人游戏,游戏开始时分别发给玩家数量相等的牌,玩家均不能看到自己和对方的牌(即盲打 ...
- 东哥手把手带你刷二叉树|第三期
学算法认准 labuladong 后台回复进群一起力扣???? 读完本文,你可以去力扣解决: 652.寻找重复子树(Medium) 接前文 手把手带你刷二叉树(第一期)和 手把手带你刷二叉树(第二期) ...
- LeetCode算法题8:递归和回溯1
文章目录 前言 回溯算法: 一.合并两个有序链表(简单,可略过) 迭代遍历 一开始没有想到的递归解法 二.反转链表 迭代遍历(头插法): 递归: 三.组合 回溯: 四.全排列 回溯(交换): 回溯: ...
- LeetCode算法题7:DFS和BFS
文章目录 前言 深度优先搜索算法伪代码: 广度优先搜索算法伪代码: 一.图像渲染 DFS: BFS: 上面BFS算法存在的问题: 修改 1: 修改 2: 二.岛屿的最大面积 DFS: BFS : 三. ...
- LeetCode算法题6:滑动窗口*
文章目录 前言 一.无重复字符的最长子串 思路 1: 思路 2: 二.字符串的排列 思路 1: 思路 2: 思路 3: 思路 3 plus: 思路 3 plus plus: 思路 4: 思路 5: 总 ...
- LeetCode算法题5:双指针
文章目录 前言 一.有序数组的平方 二.轮转数组 三.移动零 四.两数之和 II - 输入有序数组 五.反转字符串 六.反转字符串中的单词 III 七.链表的中间结点 八.删除链表的倒数第 N 个结点 ...
- LeetCode算法题4:二分查找及扩展应用
文章目录 前言 一.二分查找 二.第一个错误的版本 三.搜索插入位置 总结 前言 Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/ ...
- 啊这,一道数组去重的算法题把东哥整不会了…
学算法认准 labuladong 东哥带你手把手撕力扣???? 点击下方卡片即可搜索???? 想啥呢?labuladong 怎么可能被整不会?只是东哥又发现了一个有趣的套路,所以写了篇文章分享给大家~ ...
最新文章
- 使用CleanIISLog清除IIS记录
- python安装教程32位-python为什么要装32位的
- python安装requests第三方模块
- Re-Order Buffer
- jQuery 插件使用记录
- [原]批量删除VSS产生的scc文件
- python创建类mymath_构建DLL(MyMathFuncs)以在Python Ctypes中使用
- CI520读卡芯片 软硬件兼容替换CV520
- 【python学习笔记】Python对经纬度处理
- Mac电脑怎样网络在线重装系统
- mysql的month_MySQL month()函数
- c语言中的内存4区域模型(堆,栈,全局区,代码区)
- matplotlib设置颜色、标记、线条,让你的图像更加丰富
- 获取所有复选框选中状态的id
- Excel分组行转列
- aliPay支付宝APP支付操作流程
- 租房小程序怎么样?租房小程序可以提供哪些价值?
- wordpress个人博客申请Let’s Encrypt免费SSL证书
- 制造业管理系统如何帮助企业做好物料编码管理?
- 如何向公众号添加的文档的链接
热门文章
- 好用的java图形验证码
- 使用 CSS3 jQuery 制作漂亮的书签动画
- OpenSSL密码库算法笔记——第1.2.2章 comba乘法
- 3D全景具备哪些特点?又有哪些有趣的交互功能?
- 软件测试岗一位小伙伴的~同花顺一面(面经)
- 前端/运维电话面试被问到的问题
- 苹果home键在哪里设置_五毛特效?苹果X的Home键又回来了!
- cyq.data mysql_终于等到你:CYQ.Data V5系列 (ORM数据层)最新版本开源了
- 翁恺老师全套C语言课程笔记(本菜鸟正在学习)
- w ndows7如果连接宽带,win7怎么创建桌面宽带连接?