878. Nth Magical Number
Problem Description
A magical number is defined as a positive integer that is divisible by either a
or b
.
You are given three integers:
n
: the position we're looking fora
: first divisorb
: second divisor
Your task is to find the n
-th smallest magical number. Since this number can be very large, return the result modulo 10^9 + 7
.
For example, if a = 2
and b = 3
, the magical numbers in order would be: 2, 3, 4, 6, 8, 9, 10, 12, ... (numbers divisible by 2 or 3).
The solution uses binary search combined with the inclusion-exclusion principle. For any number x
, we can count how many magical numbers are less than or equal to x
using the formula: x // a + x // b - x // lcm(a, b)
. This counts numbers divisible by a
, plus numbers divisible by b
, minus the overlap (numbers divisible by both, which is the least common multiple). The binary search finds the smallest number where exactly n
magical numbers exist up to that point.
Intuition
The key insight is that we need to find the n-th number in a sequence where numbers are divisible by either a
or b
. Instead of generating all magical numbers one by one (which would be too slow), we can use a counting approach.
For any given number x
, we can quickly calculate how many magical numbers exist from 1 to x
:
- Numbers divisible by
a
:x // a
- Numbers divisible by
b
:x // b
- But wait - we've double-counted numbers divisible by both
a
andb
!
This is where the inclusion-exclusion principle comes in. Numbers divisible by both a
and b
are exactly those divisible by lcm(a, b)
. So the correct count is: x // a + x // b - x // lcm(a, b)
.
Now we have a way to count magical numbers up to any value x
. To find the n-th magical number, we need to find the smallest x
such that there are exactly n
magical numbers from 1 to x
. This is a perfect scenario for binary search!
The search space can be bounded by observing that the n-th magical number cannot exceed n * min(a, b)
(in the worst case, we'd take every multiple of the smaller number). The solution uses (a + b) * n
as an upper bound, which is safe but slightly larger.
Using bisect_left
with our counting function as the key, we can efficiently find the exact position where we have n
magical numbers, giving us our answer.
Learn more about Math and Binary Search patterns.
Solution Approach
The solution implements a binary search using Python's bisect_left
function with a custom key function to find the n-th magical number.
Step 1: Calculate the Least Common Multiple (LCM)
c = lcm(a, b)
We need the LCM to apply the inclusion-exclusion principle correctly. Python's [math](/problems/math-basics)
module provides the lcm
function directly.
Step 2: Define the Search Range
r = (a + b) * n
This sets an upper bound for our binary search. The n-th magical number will definitely be less than (a + b) * n
, giving us a safe search range from 0 to r
.
Step 3: Binary Search with Custom Key Function
bisect_left(range(r), x=n, key=lambda x: x // a + x // b - x // c)
This is the core of the solution:
range(r)
creates our search space from 0 to r-1- The
key
functionlambda x: x // a + x // b - x // c
counts how many magical numbers exist up to and includingx
x // a
: count of numbers divisible bya
x // b
: count of numbers divisible byb
x // c
: count of numbers divisible by both (to remove double-counting)
bisect_left
finds the leftmost position where the count equalsn
Step 4: Apply Modulo
% mod
Since the answer can be very large, we return it modulo 10^9 + 7
as required.
The beauty of this approach is that bisect_left
automatically handles finding the exact n-th magical number by searching for the smallest value x
where the count of magical numbers up to x
is at least n
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's find the 4th magical number where a = 2
and b = 3
.
Step 1: Calculate LCM
lcm(2, 3) = 6
(since 2 and 3 are coprime)
Step 2: Set up search range
- Upper bound:
(2 + 3) * 4 = 20
- We'll search from 0 to 20
Step 3: Binary search process
Let's trace through how bisect_left
works with our counting function:
- The key function for any value
x
is:x // 2 + x // 3 - x // 6
Let's calculate counts for several values:
x = 1
:1//2 + 1//3 - 1//6 = 0 + 0 - 0 = 0
magical numbersx = 2
:2//2 + 2//3 - 2//6 = 1 + 0 - 0 = 1
magical number (just 2)x = 3
:3//2 + 3//3 - 3//6 = 1 + 1 - 0 = 2
magical numbers (2, 3)x = 4
:4//2 + 4//3 - 4//6 = 2 + 1 - 0 = 3
magical numbers (2, 3, 4)x = 5
:5//2 + 5//3 - 5//6 = 2 + 1 - 0 = 3
magical numbers (2, 3, 4)x = 6
:6//2 + 6//3 - 6//6 = 3 + 2 - 1 = 4
magical numbers (2, 3, 4, 6)
The binary search looks for where the count first reaches 4:
- Start with range [0, 20]
- Middle values are tested, narrowing down the range
- Eventually finds that at
x = 6
, we have exactly 4 magical numbers
Step 4: Return result
- The 4th magical number is 6
- Apply modulo:
6 % (10^9 + 7) = 6
Verification: The magical numbers in order are 2, 3, 4, 6... and indeed the 4th one is 6.
The key insight is that we never generated the actual sequence - we just counted how many magical numbers exist up to each point and used binary search to find where that count equals n.
Solution Implementation
1import math
2from bisect import bisect_left
3
4class Solution:
5 def nthMagicalNumber(self, n: int, a: int, b: int) -> int:
6 """
7 Find the nth magical number.
8 A magical number is a positive integer that is divisible by either a or b.
9
10 Args:
11 n: The position of the magical number to find
12 a: First divisor
13 b: Second divisor
14
15 Returns:
16 The nth magical number modulo 10^9 + 7
17 """
18 # Define the modulo constant for the result
19 MOD = 10**9 + 7
20
21 # Calculate the least common multiple of a and b
22 # This helps avoid double-counting numbers divisible by both a and b
23 lcm_value = (a * b) // math.gcd(a, b)
24
25 # Set an upper bound for binary search
26 # The nth magical number won't exceed n * min(a, b), but we use a safer upper bound
27 upper_bound = (a + b) * n
28
29 # Binary search to find the nth magical number
30 # The key function counts how many magical numbers are <= x
31 # Formula: numbers divisible by a + numbers divisible by b - numbers divisible by both
32 result = bisect_left(
33 range(upper_bound),
34 x=n,
35 key=lambda x: x // a + x // b - x // lcm_value
36 )
37
38 # Return the result modulo 10^9 + 7
39 return result % MOD
40
1class Solution {
2 // Modulo value for the result
3 private static final int MOD = (int) 1e9 + 7;
4
5 /**
6 * Finds the nth magical number.
7 * A magical number is a positive integer that is divisible by either a or b.
8 *
9 * @param n the position of the magical number to find
10 * @param a first divisor
11 * @param b second divisor
12 * @return the nth magical number modulo 10^9 + 7
13 */
14 public int nthMagicalNumber(int n, int a, int b) {
15 // Calculate LCM using the formula: LCM(a,b) = a * b / GCD(a,b)
16 int lcm = a * b / gcd(a, b);
17
18 // Binary search bounds
19 // Left bound starts at 0
20 long left = 0;
21 // Right bound is an upper estimate: (a + b) * n ensures we have enough range
22 long right = (long) (a + b) * n;
23
24 // Binary search to find the nth magical number
25 while (left < right) {
26 // Calculate middle point (using unsigned right shift to avoid overflow)
27 long mid = (left + right) >>> 1;
28
29 // Count how many magical numbers are less than or equal to mid
30 // Using inclusion-exclusion principle:
31 // Count(divisible by a) + Count(divisible by b) - Count(divisible by both a and b)
32 long count = mid / a + mid / b - mid / lcm;
33
34 if (count >= n) {
35 // If we have at least n magical numbers, search in the left half
36 right = mid;
37 } else {
38 // Otherwise, search in the right half
39 left = mid + 1;
40 }
41 }
42
43 // Return the result modulo MOD
44 return (int) (left % MOD);
45 }
46
47 /**
48 * Calculates the Greatest Common Divisor using Euclidean algorithm.
49 *
50 * @param a first number
51 * @param b second number
52 * @return the GCD of a and b
53 */
54 private int gcd(int a, int b) {
55 // Base case: if b is 0, GCD is a
56 // Recursive case: GCD(a, b) = GCD(b, a % b)
57 return b == 0 ? a : gcd(b, a % b);
58 }
59}
60
1using ll = long long;
2
3class Solution {
4public:
5 const int MOD = 1e9 + 7;
6
7 int nthMagicalNumber(int n, int a, int b) {
8 // Calculate LCM of a and b to handle overlapping multiples
9 int lcm_ab = lcm(a, b);
10
11 // Binary search range: [0, upper_bound]
12 // Upper bound is set conservatively as (a + b) * n
13 ll left = 0;
14 ll right = 1LL * (a + b) * n;
15
16 // Binary search to find the nth magical number
17 while (left < right) {
18 ll mid = left + (right - left) / 2;
19
20 // Count magical numbers <= mid using inclusion-exclusion principle
21 // Numbers divisible by a: mid/a
22 // Numbers divisible by b: mid/b
23 // Numbers divisible by both (LCM): mid/lcm_ab
24 ll count = mid / a + mid / b - mid / lcm_ab;
25
26 if (count >= n) {
27 // mid might be the answer or answer is smaller
28 right = mid;
29 } else {
30 // Need to search in larger values
31 left = mid + 1;
32 }
33 }
34
35 // Return the result modulo MOD
36 return left % MOD;
37 }
38};
39
1const MOD = 1e9 + 7;
2
3function nthMagicalNumber(n: number, a: number, b: number): number {
4 // Helper function to calculate Greatest Common Divisor using Euclidean algorithm
5 function gcd(x: number, y: number): number {
6 while (y !== 0) {
7 const temp = y;
8 y = x % y;
9 x = temp;
10 }
11 return x;
12 }
13
14 // Helper function to calculate Least Common Multiple
15 function lcm(x: number, y: number): number {
16 return Math.floor(x * y / gcd(x, y));
17 }
18
19 // Calculate LCM of a and b to handle overlapping multiples
20 const lcmAB = lcm(a, b);
21
22 // Binary search range: [0, upperBound]
23 // Upper bound is set conservatively as (a + b) * n
24 let left = 0;
25 let right = (a + b) * n;
26
27 // Binary search to find the nth magical number
28 while (left < right) {
29 const mid = Math.floor(left + (right - left) / 2);
30
31 // Count magical numbers <= mid using inclusion-exclusion principle
32 // Numbers divisible by a: mid/a
33 // Numbers divisible by b: mid/b
34 // Numbers divisible by both (LCM): mid/lcmAB
35 const count = Math.floor(mid / a) + Math.floor(mid / b) - Math.floor(mid / lcmAB);
36
37 if (count >= n) {
38 // mid might be the answer or answer is smaller
39 right = mid;
40 } else {
41 // Need to search in larger values
42 left = mid + 1;
43 }
44 }
45
46 // Return the result modulo MOD
47 return left % MOD;
48}
49
Time and Space Complexity
Time Complexity: O(log(n * max(a, b)))
The algorithm uses binary search via bisect_left
on a range from 0 to r = (a + b) * n
. The binary search performs O(log(r))
iterations, where r = (a + b) * n
. Since a
and b
are positive integers, this simplifies to O(log(n * (a + b)))
, which can be expressed as O(log(n * max(a, b)))
in the worst case.
For each iteration of the binary search, the key function lambda x: x // a + x // b - x // c
is evaluated. This function performs:
- Three integer division operations:
O(1)
- Two addition/subtraction operations:
O(1)
- The
lcm(a, b)
calculation is done once before the binary search:O(log(min(a, b)))
using the GCD algorithm
Therefore, the overall time complexity is dominated by the binary search: O(log(n * max(a, b)))
.
Space Complexity: O(1)
The algorithm uses only a constant amount of extra space:
- Variables
mod
,c
, andr
each requireO(1)
space - The
bisect_left
function usesO(1)
auxiliary space for binary search - The range object in Python 3 is a lazy iterator that doesn't create the full list, using
O(1)
space - The lambda function doesn't create any additional data structures
Thus, the total space complexity is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect LCM Calculation Leading to Integer Overflow
One of the most common mistakes is calculating LCM incorrectly, especially when a
and b
are large numbers. The naive formula lcm = (a * b) // gcd(a, b)
can cause integer overflow during the multiplication step, even though the final LCM value might fit within bounds.
Problematic Code:
# This can overflow when a and b are large lcm_value = (a * b) // math.gcd(a, b)
Solution:
Use Python's built-in math.lcm()
function or rearrange the calculation to avoid overflow:
# Option 1: Use built-in function (Python 3.9+) lcm_value = math.lcm(a, b) # Option 2: Divide first to prevent overflow gcd_value = math.gcd(a, b) lcm_value = (a // gcd_value) * b
2. Off-by-One Error in Binary Search Result
The bisect_left
function returns the index where the count of magical numbers first reaches n
, but this index itself is the answer (the nth magical number), not index + 1
or index - 1
. A common mistake is adjusting the result unnecessarily.
Problematic Code:
# Wrong: Adding or subtracting from the result result = bisect_left(...) + 1 # Incorrect!
Solution:
The index returned by bisect_left
directly represents the nth magical number:
result = bisect_left(
range(upper_bound),
x=n,
key=lambda x: x // a + x // b - x // lcm_value
)
# No adjustment needed - result is already correct
3. Insufficient Upper Bound for Binary Search
Setting too small an upper bound can cause the binary search to fail to find the answer. While n * min(a, b)
seems logical, it's not always sufficient when a
and b
have a large GCD.
Problematic Code:
# May be too small in some cases
upper_bound = n * min(a, b)
Solution: Use a more conservative upper bound that guarantees the nth magical number falls within range:
# Safe upper bound that works for all cases
upper_bound = n * max(a, b)
# Or even safer:
upper_bound = (a + b) * n
4. Forgetting the Modulo Operation
The problem explicitly asks for the result modulo 10^9 + 7
, but it's easy to forget this step since the binary search itself doesn't require it.
Problematic Code:
def nthMagicalNumber(self, n: int, a: int, b: int) -> int:
# ... binary search logic ...
return result # Missing modulo!
Solution: Always apply the modulo operation before returning:
MOD = 10**9 + 7 return result % MOD
What's the relationship between a tree and a graph?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!