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 Implementation

1import math
2
3class Solution:
4    def nthMagicalNumber(self, n: int, a: int, b: int) -> int:
5        """
6        Find the nth magical number.
7        A magical number is a positive integer that is divisible by either a or b.
8
9        Args:
10            n: The position of the magical number to find
11            a: First divisor
12            b: Second divisor
13
14        Returns:
15            The nth magical number modulo 10^9 + 7
16        """
17        MOD = 10**9 + 7
18
19        # Calculate the least common multiple of a and b
20        lcm_value = (a * b) // math.gcd(a, b)
21
22        def feasible(x: int) -> bool:
23            """Check if there are at least n magical numbers <= x."""
24            count = x // a + x // b - x // lcm_value
25            return count >= n
26
27        # Binary search for the nth magical number
28        # Search space: [1, n * min(a, b)]
29        left, right = 1, n * min(a, b)
30        first_true_index = -1
31
32        while left <= right:
33            mid = (left + right) // 2
34            if feasible(mid):
35                first_true_index = mid
36                right = mid - 1
37            else:
38                left = mid + 1
39
40        return first_true_index % MOD
41
1class Solution {
2    private static final int MOD = (int) 1e9 + 7;
3
4    public int nthMagicalNumber(int n, int a, int b) {
5        // Calculate LCM using the formula: LCM(a,b) = a * b / GCD(a,b)
6        long lcm = (long) a * b / gcd(a, b);
7
8        // Binary search bounds: [1, n * min(a, b)]
9        long left = 1;
10        long right = (long) n * Math.min(a, b);
11        long firstTrueIndex = -1;
12
13        while (left <= right) {
14            long mid = left + (right - left) / 2;
15
16            // Count magical numbers <= mid using inclusion-exclusion
17            long count = mid / a + mid / b - mid / lcm;
18
19            if (count >= n) {
20                // Feasible: record answer and search for smaller
21                firstTrueIndex = mid;
22                right = mid - 1;
23            } else {
24                // Not feasible: need larger value
25                left = mid + 1;
26            }
27        }
28
29        return (int) (firstTrueIndex % MOD);
30    }
31
32    private int gcd(int a, int b) {
33        return b == 0 ? a : gcd(b, a % b);
34    }
35}
36
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
9        ll lcm_ab = lcm((ll)a, (ll)b);
10
11        // Binary search range: [1, n * min(a, b)]
12        ll left = 1;
13        ll right = 1LL * n * min(a, b);
14        ll firstTrueIndex = -1;
15
16        while (left <= right) {
17            ll mid = left + (right - left) / 2;
18
19            // Count magical numbers <= mid using inclusion-exclusion
20            ll count = mid / a + mid / b - mid / lcm_ab;
21
22            if (count >= n) {
23                // Feasible: record answer and search for smaller
24                firstTrueIndex = mid;
25                right = mid - 1;
26            } else {
27                // Not feasible: need larger value
28                left = mid + 1;
29            }
30        }
31
32        return firstTrueIndex % MOD;
33    }
34};
35
1const MOD = 1e9 + 7;
2
3function nthMagicalNumber(n: number, a: number, b: number): number {
4    function gcd(x: number, y: number): number {
5        while (y !== 0) {
6            const temp = y;
7            y = x % y;
8            x = temp;
9        }
10        return x;
11    }
12
13    function lcm(x: number, y: number): number {
14        return Math.floor(x * y / gcd(x, y));
15    }
16
17    const lcmAB = lcm(a, b);
18
19    // Binary search range: [1, n * min(a, b)]
20    let left = 1;
21    let right = n * Math.min(a, b);
22    let firstTrueIndex = -1;
23
24    while (left <= right) {
25        const mid = Math.floor((left + right) / 2);
26
27        // Count magical numbers <= mid using inclusion-exclusion
28        const count = Math.floor(mid / a) + Math.floor(mid / b) - Math.floor(mid / lcmAB);
29
30        if (count >= n) {
31            // Feasible: record answer and search for smaller
32            firstTrueIndex = mid;
33            right = mid - 1;
34        } else {
35            // Not feasible: need larger value
36            left = mid + 1;
37        }
38    }
39
40    return firstTrueIndex % MOD;
41}
42

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 5-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.

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. Using Wrong Binary Search Template Variant

The most critical mistake is using a different binary search variant that doesn't match the standard template. The template-compliant approach uses first_true_index to track the answer, while left <= right, and right = mid - 1 when feasible.

Wrong approach (alternative variant):

left, right = 1, n * min(a, b)
while left < right:
    mid = (left + right) // 2
    if count(mid) >= n:
        right = mid  # WRONG: should be right = mid - 1
    else:
        left = mid + 1
return left  # WRONG: should track first_true_index

Correct template-compliant approach:

left, right = 1, n * min(a, b)
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if count(mid) >= n:
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index % MOD

2. Incorrect LCM Calculation Leading to Integer Overflow

The naive formula lcm = (a * b) // gcd(a, b) can cause integer overflow during the multiplication step, especially in languages like Java/C++.

Solution:

# 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

3. 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.

Solution: Always apply the modulo operation before returning:

MOD = 10**9 + 7
return first_true_index % MOD

4. Edge Cases with first_true_index

When using the template, always initialize first_true_index = -1 and handle the case where no valid answer is found (though for this problem, an answer is guaranteed to exist).

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used to implement recursion?


Recommended Readings

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

Load More