Given four integers N
, A
, and B
, you are asked to find the N
th "magical number". A "magical number" is defined as a positive integer that is divisible by either A
or B
.
A
or B
, sorted in increasing order and without duplicates.N
th such number, modulo 10^9 + 7
.1 ≤ N ≤ 10^9
, 2 ≤ A, B ≤ 4 × 10^4
.
At first glance, you might consider generating all the numbers divisible by A
or B
in order, and picking the N
th one. However, given that N
can be as large as 10^9
, this brute-force approach is far too slow.
We need to find a more efficient way to determine the N
th magical number without generating the entire sequence. The key insight is to reframe the problem: instead of finding the N
th element by enumeration, ask: "For a given number X
, how many magical numbers are less than or equal to X
?" This allows us to use a binary search to efficiently home in on the answer.
X
, the number of magical numbers less than or equal to X
is:
A
: floor(X / A)
B
: floor(X / B)
A
and B
(i.e., by LCM(A, B)
) are counted twice, so subtract floor(X / LCM(A, B))
So, count(X) = floor(X / A) + floor(X / B) - floor(X / LCM(A, B))
.
X
such that count(X) ≥ N
. We can perform a binary search between min(A, B)
and N * min(A, B)
(since the N
th magical number can't be larger than N
times the smallest of A
or B
).
A
and B
. The LCM can be found as LCM(A,B) = (A * B) / GCD(A,B)
.
10^9 + 7
.
Let's walk through an example: N = 5
, A = 2
, B = 4
.
count(6) = 6//2 + 6//4 - 6//4 = 3 + 1 - 1 = 3
(too small)count(8) = 4 + 2 - 2 = 4
(still too small)count(9) = 4 + 2 - 2 = 4
count(10) = 5 + 2 - 2 = 5
(just right!)Binary search identifies 10 as the smallest number with at least 5 magical numbers ≤ it.
N
would take O(N) time and O(1) space. This is infeasible for large N.
N * min(A, B)
, so the number of steps is O(log(N * min(A, B))) ≈ O(log N).The key insight for the Nth Magical Number problem is to avoid brute-force enumeration by using a counting function and binary search. By counting how many magical numbers are ≤ X, and adjusting X using binary search, we efficiently find the answer even for very large N. The use of LCM ensures we don't double-count numbers divisible by both A and B. This approach is elegant, efficient, and leverages classic algorithmic techniques.
import math
class Solution:
def nthMagicalNumber(self, N: int, A: int, B: int) -> int:
MOD = 10**9 + 7
def lcm(x, y):
return x * y // math.gcd(x, y)
L = lcm(A, B)
left, right = 1, N * min(A, B)
while left < right:
mid = (left + right) // 2
# Count how many magical numbers ≤ mid
if mid // A + mid // B - mid // L < N:
left = mid + 1
else:
right = mid
return left % MOD
class Solution {
public:
int nthMagicalNumber(int N, int A, int B) {
const int MOD = 1e9 + 7;
long long lcm = (long long)A * B / std::__gcd(A, B);
long long left = 1, right = (long long)N * std::min(A, B);
while (left < right) {
long long mid = left + (right - left) / 2;
if (mid / A + mid / B - mid / lcm < N)
left = mid + 1;
else
right = mid;
}
return left % MOD;
}
};
class Solution {
public int nthMagicalNumber(int N, int A, int B) {
int MOD = 1000000007;
long lcm = (long)A * B / gcd(A, B);
long left = 1, right = (long)N * Math.min(A, B);
while (left < right) {
long mid = left + (right - left) / 2;
if (mid / A + mid / B - mid / lcm < N) {
left = mid + 1;
} else {
right = mid;
}
}
return (int)(left % MOD);
}
private int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
}
var nthMagicalNumber = function(N, A, B) {
const MOD = 1e9 + 7;
function gcd(a, b) {
while (b !== 0) {
let temp = b;
b = a % b;
a = temp;
}
return a;
}
function lcm(a, b) {
return a * b / gcd(a, b);
}
let L = lcm(A, B);
let left = 1, right = N * Math.min(A, B);
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (Math.floor(mid / A) + Math.floor(mid / B) - Math.floor(mid / L) < N)
left = mid + 1;
else
right = mid;
}
return left % MOD;
};