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. LeetCode 232:用栈实现队列 Implement Queue using Stacks

    题目: 使用栈实现队列的下列操作: push(x) -- 将一个元素放入队列的尾部. pop() -- 从队列首部移除元素. peek() -- 返回队列首部的元素. empty() -- 返回队列是 ...

  2. 我是如何理解并使用maven的

    前言 一直想写一篇关于Maven的文章,但是不知如何下笔,如果说能使用,会使用Maven的话,一.两个小时足矣,不需要搞懂各种概念.那么给大家来分享下我是如何理解并使用maven的. 什么是Maven ...

  3. Mybatis中的association用法

    这篇文章我们将来学习一些 association 用法 表结构 DROP TABLE IF EXISTS `student`; CREATE TABLE `student` (  `id` int(1 ...

  4. SSM手动实现分页逻辑---非PageHelper方式

    第一种方法:查询出所有数据再分页 分析: 分页时,需要获得前台传来的两个参数,分别为pageNo(第几页数据),pageSize(每页的条数); 根据这两个参数来计算出前端需要的数据是查出数据list ...

  5. iis7 下配置 ASP.NET MVC 项目遇到的问题 (WIN7 64位 旗舰版 第一次配置站点)

    转自 https://www.cnblogs.com/Leo_wl/p/3866625.html,再次感谢 指定的目录或文件在 Web 服务器上不存在. URL 拼写错误. 某个自定义筛选器或模块(如 ...

  6. java中级,知识点归纳(一)

    一.接口和抽象类的区别 抽象类中可以含有构造方法,而接口内不能有. 抽象类中可以有普通成员变量,而接口中不能有. 抽象类中可以包含非抽象的普通方法,而接口中所有方法必须是抽象的,不能有非抽象的普通方法 ...

  7. 剑指 Offer——1. 二维数组中的查找

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  8. scrapy框架抓取表情包/(python爬虫学习)

    抓取网址:https://www.doutula.com/photo/list/?page=1 1.创建爬虫项目:scrapy startproject biaoqingbaoSpider 2.创建爬 ...

  9. Java开发常用知识点总结

    docker exec -it imageId redis-cli docker container ls -a docker rm containerId 复制目录&文件 cp -r /ro ...

  10. SQL行转列,列转行

    SQL 行转列,列转行 行列转换在做报表分析时还是经常会遇到的,今天就说一下如何实现行列转换吧. 行列转换就是如下图所示两种展示形式的互相转换 行转列 假如我们有下表: SELECT * FROM s ...