A positive integer is *magical* if it is divisible by either A or B.

Return the N-th magical number.  Since the answer may be very large, return it modulo 10^9 + 7.

Example 1:

Input: N = 1, A = 2, B = 3
Output: 2

Example 2:

Input: N = 4, A = 2, B = 3
Output: 6

Example 3:

Input: N = 5, A = 2, B = 4
Output: 10

Example 4:

Input: N = 3, A = 6, B = 4
Output: 8

Note:

  1. 1 <= N <= 10^9
  2. 2 <= A <= 40000
  3. 2 <= B <= 40000

这道题定义了一种神奇正整数,就是能同时被给定的正整数A或B整除的数,让我们返回第N个神奇的数字,暗示了这个数字可能很大,要对一个超大数取余。又是对这个 1e9+7 取余,博主下意识的反应是用动态规划 Dynamic Programming 来做,但其实是不能的,因为每个数字能不能被A或B整除是相对独立的,并不会跟之前的状态有联系,这样就不好写出状态转移方程了,所以这并不是一道 DP 题。首先来想,对于 [1, n] 中的数,能整除A的有多少个,举例来说吧,假如 n=17,A=2,那么 17 以内能整除2的就有 2,4,6,8,10,12,14,16,这八个数字,貌似正好是 n/A=17/2=8。再来看其他例子,比如 n=17,B=3,那么 17 以内能整除3的就有 3,6,9,12,15,这五个数字,貌似也是 n/B=17/3=5。那么能被A或B整除的个数呢,比如 n=17,A=2,B=3,那么 17 以内能整除2或3的数字有 2,3,4,6,8,9,10,12,14,15,16,这十一个数字,并不是 n/A + n/B = 8+5 = 13,为啥呢?因为有些数字重复计算了,比如 6,12,这两个数字都加了两次,我们发现这两个数字都是既可以整除A又可以整除B的,只要把这两个数字减去 13-2=11,就是所求的了。怎么找同时能被A和B整除的数呢,其实第一个这样的数就是A和B的最小公倍数 Least Common Multiple,所有能被A和B的最小公倍数整除的数字一定能同时整除A和B。那么最小公倍数 LCM 怎么算呢?这应该是小学数学的知识了吧,就是A乘以B除以最大公约数 Greatest Common Divisor,这个最大公约数就不用多说了吧,也是小学的内容,是最大的能同时整除A和B的数。

明白了这些,我们就知道了对于任意小于等于数字x的且能被A或B整除的正整数的个数为 x/A + x/B - x/lcm(A,B)。所以我们需要让这个式子等于N,然后解出x的值即为所求。直接根据式子去求解x得到的不一定是正整数,我们可以反其道而行之,带确定的x值进入等式,算出一个结果,然后跟N比较大小,根据这个大小来决定新的要验证的x值,这不就是典型的二分搜索法么。确定了要使用 Binary Search 后,就要来确定x值的范围了,x值最小能取到A和B中的较小值,由于A和B最小能取到2,所以x的最小值也就是2。至于最大值,还是根据上面的等式,x能取到的最大值是 N*min(A,B),根据题目中N和A,B的范围,可以推出最大值不会超过 1e14,这个已经超过整型最大值了,所以我们初始化的变量都要用长整型。然后就是进入 while 循环了,判定条件是上面写的那个等式,其实这是博主之前的总结帖 LeetCode Binary Search Summary 二分搜索法小结 中的第四类,用子函数当作判断关系,不是简单的用 mid 来判断,而是要通过 mid 来计算出需要比较的值。若计算值比N小,则去右半段,反之左半段,最后别忘了对M取余即可,参见代码如下:

解法一:

class Solution {
public:
int nthMagicalNumber(int N, int A, int B) {
long lcm = A * B / gcd(A, B), left = 2, right = 1e14, M = 1e9 + 7;
while (left < right) {
long mid = left + (right - left) / 2;
if (mid / A + mid / B - mid / lcm < N) left = mid + 1;
else right = mid;
}
return right % M;
}
int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
};

下面这种方法就比较 tricky 了,完全是利用数学功底来解的,连二分搜索都不用,常数级的时间复杂度,碉堡了有木有?!这里主要是参考了[大神 jianwu 的帖子](https://leetcode.com/problems/nth-magical-number/discuss/154965/o(1)-Mathematical-Solution-without-binary-or-brute-force-search),博主也不能说是完全理解透彻了,尝试着去讲解一下吧。我们用这个例子来讲解吧 A=3,B=5,N=10,前面的分析提到了,只有在A和B的最小公倍数 LCM 处,或者是能除以这个 LCM 的数字的地方,才会出现重复。3和5的最小公倍数是 15,所以对于所有小于15的数字x,能整除A的数字有 x/A 个,能整除B的数字有 x/B 个,若把每一个 LCM 看作一个 block 的话,这个区间内分别能整除A和B的数字是没有重复的,所以这个区间的长度是 len = lcm/A + lcm/B - 1 = 15/3 + 15/5 - 1 = 7。下面要算的就是N里面有多少个 block,并且余数是多少,因为我们可以通过 LCM 快速来定位 block 的边界位置,只要知道了偏移量,就能求出正确的神奇数字了。block 的个数是通过 N/len = 10/7 = 1,余数是通过 N%len = 10%7 = 3 计算的。我们可以通过 block 的个数和长度快速定位到 15,这里是不能直接加上余数3的,因为余数表示的是 15 后面的第三个能被3或5整除的数,18,20,21,所以答案是 21,这里我们需要算出这个偏移量 21-15=6,从而才能得到正确结果。怎么计算呢?想一下,15 后面第三个能被3整除的数是 18,21,24,所以只看3的话偏移量是 3/(1.0/3)=9,而 15 后面第三个能被5整除的数是 20,25,30,偏移量是 3/(1.0/5)=15,但是正确的偏移量应该是考虑两种情况的总和,所以应该是 nearest=3/(1.0/3 + 1.0/5)=5.625,但我们的偏移量一定要是个整数,所以我们再用一个取整的过程 min(ceil(nearest/3) x 3, ceil(nearest/5) x 5) = 6,最终就可以得到正确的偏移量,加上 15,就是正确的结果了,参见代码如下:


解法二:

class Solution {
public:
int nthMagicalNumber(int N, int A, int B) {
long lcm = A * B / gcd(A, B), M = 1e9 + 7;
long len = lcm / A + lcm / B - 1, cnt = N / len, rem = N % len;
double nearest = rem / (1.0 / A + 1.0 / B);
int remIdx = min(ceil(nearest / A) * A, ceil(nearest / B) * B);
return (cnt * lcm + remIdx) % M;
}
int gcd(int a, int b) {
return (b == 0) ? a : gcd(b, a % b);
}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/878

参考资料:

https://leetcode.com/problems/nth-magical-number/

https://leetcode.com/problems/nth-magical-number/discuss/154613/C%2B%2BJavaPython-Binary-Search

https://leetcode.com/problems/nth-magical-number/discuss/154965/o(1)-Mathematical-Solution-without-binary-or-brute-force-search

[LeetCode All in One 题目讲解汇总(持续更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)

[LeetCode] 878. Nth Magical Number 第N个神奇数字的更多相关文章

  1. 878. Nth Magical Number

    A positive integer is magical if it is divisible by either A or B. Return the N-th magical number.  ...

  2. 【leetcode】878. Nth Magical Number

    题目如下: 解题思路:本题求的是第N个Magical Number,我们可以很轻松的知道这个数的取值范围 [min(A,B), N*max(A,B)].对于知道上下界求具体数字的题目,可以考虑用二分查 ...

  3. [Swift]LeetCode878. 第 N 个神奇数字 | Nth Magical Number

    A positive integer is magical if it is divisible by either A or B. Return the N-th magical number.  ...

  4. [LeetCode] 1137. N-th Tribonacci Number

    Description e Tribonacci sequence Tn is defined as follows: T0 = 0, T1 = 1, T2 = 1, and Tn+3 = Tn + ...

  5. C++版 - Leetcode 400. Nth Digit解题报告

    leetcode 400. Nth Digit 在线提交网址: https://leetcode.com/problems/nth-digit/ Total Accepted: 4356 Total ...

  6. 【LeetCode】264. Ugly Number II 解题报告(Java & Python)

    标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ https://leetcode.com/prob ...

  7. 【LeetCode】481. Magical String 解题报告(Python)

    [LeetCode]481. Magical String 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http:/ ...

  8. (斐波那契总结)Write a method to generate the nth Fibonacci number (CC150 8.1)

    根据CC150的解决方式和Introduction to Java programming总结: 使用了两种方式,递归和迭代 CC150提供的代码比较简洁,不过某些细节需要分析. 现在直接运行代码,输 ...

  9. 【一天一道LeetCode】#260. Single Number III

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given a ...

随机推荐

  1. Flask-Moment本地化日期和时间

    moment.js客户端开源代码库,可以在浏览器中渲染日期和时间.Flask-Moment是一个flask程序扩展,能把moment.js集成到Jinja2模板中. 1.安装 pip install ...

  2. 安装win10和ubuntu双系统

    2019-06-22 ​ 最近找了一份新的工作,要用到linux,由于之前基本上没有接触过这方面的东西,所以今天捣鼓一下,安装win10和linux双系统,办公研发双不误. 如果在安装的过程中遇到什么 ...

  3. Winform中设置ZedGraph多条Y轴时坐标轴左右显示设置

    场景 Winform中实现ZedGraph的多条Y轴(附源码下载): https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/1001322 ...

  4. Java引用类型原理深度剖析,看完文章,90%的人都收藏了

    本文为synchronized系列第二篇.主要内容为分析偏向锁的实现. 偏向锁的诞生背景和基本原理在上文中已经讲过了. 本文将分为几块内容: 1.偏向锁的入口 2.偏向锁的获取流程 3.偏向锁的撤销流 ...

  5. Java生鲜电商平台-服务器部署设计与架构

    Java生鲜电商平台-服务器部署设计与架构 补充说明:Java开源生鲜电商平台-服务器部署设计与架构,指的是通过服务器正式上线整个项目,进行正式的运营. 回顾整个章节,我们涉及到以下几个方面: 1. ...

  6. SSO单点登录和CAS

    一.单点登录流程 =====客户端====== 1.拦截客户端的请求判断是否有局部的session 2.1如果有局部的session,放行请求. 2.2如果没有局部session 2.2.1请求中有携 ...

  7. Restful API接口规范

    1. 域名 应该尽量将API部署在专用域名之下. https://api.example.com 如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下. https://example.org ...

  8. 2019-07-23 类的继承和final关键字的应用

    我们称以存在的用来派生新类的类为基类,又称做父类,超类.由已存在的类派生出的新类称为派生类,又称为子类.从一个基类派生的继承称单继承,从多个基类派生的继承称为多继承.也就是说:一个类只能直接从一个类中 ...

  9. 使用IDEA创建Spring Boot项目

    第一步:根据1.2.3点鼠标哦: 第二步:点击Next 第三步:按红框框选,然后Next 第四步:选个保存路径,然后Next 第五步:点击Finish就大功告成了

  10. SVG撑满页面

    当viewBox属性固定,默认修改svg标签的宽高,svg都会按比例缩放 我们现在不想按比例缩放,需要svg撑满整个画面 这里只需为svg标签添加一个关键属性:preserveAspectRatio ...