一、问题描述
定义:一个正整数如果能被 a 或 b 整除,那么它是神奇的。
输入:给定三个整数 n , a , b ,返回第 n 个神奇的数字。因为答案可能很大,所以返回第 n 个神奇的数字对 10^9 + 7 取模后的值。
示例1:
输入:n = 1,a = 2, b = 3; 输出:2; 因为 2 是 第1个被 2 或 3 整除的数;
示例2:
输入:n = 4,a = 2, b = 3; 输出:6; 因为 6 是 第4个被 2 或 3 整除的数:2,3,4,6;
二、解题思路
1、思路1:二分查找
假定区间从0开始,往正无穷增长,容易看出,区间[0,x] 包含的“神奇数字”个数是单调递增的,也就是说,x越大,包含的神奇数字越多,假设 a 和 b 的最小公倍数为 c,根据容斥原理,神奇数字关于 x 的函数为 num = f(x) = floor(x/a) + floor(x/b) - floor(x/c);同时,对于给定的n,我们知道 x 一定不会超过 n * min(a,b)。根据上面两点,我们可以在区间[0, n * min(a,b)] 进行二分查找,直至找到num = n。注意查找时要将端点移动到分段的左端点才是n。
2、思路2:数学周期
假设 a 和 b 的最小公倍数为 c,不难发现,任意 [ k * c, (k + 1) * c] 区间内,含有的神奇数字个数是相同的,k为从0开始的任意自然数。根据这个思路,确定一个神奇数字,只需要确定他在哪个周期、周期内偏移为多少即可。
三、C++代码
1、思路1:二分查找
#include <iostream>
using namespace std;
class Solution {
public:
int MOD = 1000000007;
long long gcd(long long a, long long b) {
if (b == 0) return a;
return gcd(b, a % b);
}
long long get_num(long long x, long long a, long long b, long long c) {
return (long long)(x / a + x / b) - x / c;
}
int nthMagicalNumber(int n, int a, int b) {
long long c = ((long long)(a)*b) / (a > b ? gcd(a, b) : gcd(b, a));
long long l = (a > b ? b : a);
long long r = n * l;
long long m;
while (r >= l) {
m = (l + r) / 2;
if (get_num(m, a, b, c) >= n)
r = m - 1;
else
l = m + 1;
}
return (r + 1) % MOD;
}
};
int main()
{
Solution s;
cout << "Test 1 : a = 2, b = 3 : " << endl;
cout << "S(1,2,3) Excepted:2 Actually:" << s.nthMagicalNumber(1, 2, 3) << endl;
cout << "S(2,2,3) Excepted:3 Actually:" << s.nthMagicalNumber(2, 2, 3) << endl;
cout << "S(3,2,3) Excepted:4 Actually:" << s.nthMagicalNumber(3, 2, 3) << endl;
cout << "S(4,2,3) Excepted:6 Actually:" << s.nthMagicalNumber(4, 2, 3) << endl;
cout << "Test 2 : a = 3, b = 2 : " << endl;
cout << "S(4,3,2) Excepted:6 Actually:" << s.nthMagicalNumber(4, 3, 2) << endl;
cout << "Test 3 : a = 3, b = 3 : " << endl;
cout << "S(3,3,3) Excepted:9 Actually:" << s.nthMagicalNumber(3, 3, 3) << endl;
return 0;
}
2、思路2:数学周期
#include <iostream>
#include <vector>
//#define MOD 1000000007
using namespace std;
class Solution1 {
public:
int MOD = 1000000007;
int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
int nthMagicalNumber(int n, int a, int b) {
int space_len = (a > b ? a * b / gcd(a, b) : a * b / gcd(b, a));
int a_capatical = space_len / a;
int b_capatical = space_len / b;
int space_capatical = a_capatical + b_capatical - 1;
vector<int> space;
{
int ptr_a = 1, ptr_b = 1;
space.push_back(0);
while (ptr_a < a_capatical || ptr_b < b_capatical) {
if (ptr_a * a < ptr_b * b) {
space.push_back(ptr_a * a);
ptr_a++;
}
else {
space.push_back(ptr_b * b);
ptr_b++;
}
}
space.push_back(space_len);
}
int space_num = n / space_capatical;
int additional_step = n % space_capatical;
return (int)((((long long)(space_len % MOD) * (space_num % MOD) % MOD) + space[additional_step] % MOD) % MOD);
}
};
int main()
{
Solution1 s1;
cout << "Test 1 : a = 2, b = 3 : " << endl;
cout << "S(1,2,3) Excepted:2 Actually:" << s1.nthMagicalNumber(1, 2, 3) << endl;
cout << "S(2,2,3) Excepted:3 Actually:" << s1.nthMagicalNumber(2, 2, 3) << endl;
cout << "S(3,2,3) Excepted:4 Actually:" << s1.nthMagicalNumber(3, 2, 3) << endl;
cout << "S(4,2,3) Excepted:6 Actually:" << s1.nthMagicalNumber(4, 2, 3) << endl;
cout << "Test 2 : a = 3, b = 2 : " << endl;
cout << "S(4,3,2) Excepted:6 Actually:" << s1.nthMagicalNumber(4, 3, 2) << endl;
cout << "Test 3 : a = 3, b = 3 : " << endl;
cout << "S(3,3,3) Excepted:9 Actually:" << s1.nthMagicalNumber(3, 3, 3) << endl;
return 0;
}
四、运行结果
1、思路1:二分查找
2、思路2:数学周期
五、复杂度
时间复杂度:O(log len) = O(log (n * min(a,b)));
空间复杂度:O(1);