Facebook Pixel

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 for
  • a: first divisor
  • b: 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 and b!

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 function lambda x: x // a + x // b - x // c counts how many magical numbers exist up to and including x
    • x // a: count of numbers divisible by a
    • x // b: count of numbers divisible by b
    • x // c: count of numbers divisible by both (to remove double-counting)
  • bisect_left finds the leftmost position where the count equals n

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 Evaluator

Example 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 numbers
  • x = 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, and r each require O(1) space
  • The bisect_left function uses O(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
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More