LeetCode 878 - Nth Magical Number


https://leetcode.com/problems/nth-magical-number/
A positive integer is magical if it is divisible by either A or B.
Return the N-th magical number.  Since the answer may be very large, return it modulo 10^9 + 7.

    Example 1:
    Input: N = 1, A = 2, B = 3
    Output: 2
    
    Example 2:
    Input: N = 4, A = 2, B = 3
    Output: 6
    
    Example 3:
    Input: N = 5, A = 2, B = 4
    Output: 10
    
    Example 4:
    Input: N = 3, A = 6, B = 4
    Output: 8
    

    Note:
    1. 1 <= N <= 10^9
    2. 2 <= A <= 40000
    3. 2 <= B <= 40000
    https://blog.csdn.net/XX_123_1_RJ/article/details/81476088
    这个题目,应该有一半是数学问题,不易想到。先简单介绍一下容斥原理,我们要求两个集合的并集,可以先把两个单集合相加,然后减去他们的交集就可以(网上有大把,可以参考哦,这里只是简单一提)。
    (1)返回第 N 个神奇数字?很显然,这个神奇数字里面应该包含N个数字可以被 A 或 B 整除(这里的包含可以理解为神奇数字前面的数字,不太严谨哈)。
    (2)现在,我们可以设置两个界限,一个最小下界 0,一个最大上界10**15。
    (3)在两个上下界限内,进行二分查找,看看哪数字合适。
    (4)如何确定这个神奇数字包含的个数?有数学方法可以求出来,简单理解就是求出这个神奇数字的能被A整除的个数+被B整除的个数-能同时被A、B整除的个数 (也就用到容斥原理,具体的可以去网上深入学习哦),即可。
    https://zxi.mytechroad.com/blog/math/leetcode-878-nth-magical-number/
    Let n denote the number of numbers <= X that are divisible by either A or B.
    n = X / A + X / B – X / lcm(A, B) = X / A + X / B – X / (A * B / gcd(A, B))
    Binary search for the smallest X such that n >= N
      第一反应就是二分答案,判定一个数x内有多少个Magical Number很容易,稍微容斥一下,即
    xA+xBxlcm(A,B)

      只需要二分Nmin(A,B)内的数即可,因为必有N个A或B。
      其实之后才想到官方题解中复杂度更高的那个算法,也就是在每lcm(A, B)个数出现的Magical Number模式(个数)是一样的,因此可以看看lcm(A, B)内有多少个,然后取个模,枚举剩下的。
     int gcd(int u, int v) {
      if (v == 0) {
       return u;
      }
      return gcd(v, u % v);
     }
    
     int nthMagicalNumber(int N, int A, int B) {
      long long u = 0, v = static_cast<long long>(N) * min(A, B);
      int lcm = A / gcd(A, B) * B;
      while (u < v) {
       long long m = u + (v - u) / 2;
       if (m / A + m / B - m / lcm < N) {
        u = m + 1;
       }
       else {
        v = m;
       }
      }
      return u % kModule;
     }
    https://www.cnblogs.com/seyjs/p/9597987.html
    本题求的是第N个Magical Number,我们可以很轻松的知道这个数的取值范围 [min(A,B), N*max(A,B)]。对于知道上下界求具体数字的题目,可以考虑用二分查找。这样题目就从找出第N个Magical Number变成判断一个数是否是第N个Magical Number。假设n是其中一个Magical Number,显然n满足 n % A == 0 或者 n % B == 0。对于A来说,从A到n之间有n/A个Magical Number,而对B来说是n/B个Magical Number。但是n却并不是第(n/A + n/B)个Magical Number,因为这两者之间会有满足A * i == B * j数字出现,这样会造成重复,这样的数字有多少个呢?和A与B的最小公倍数有关,有n/LCM(A,B)个

    https://leetcode.com/problems/nth-magical-number/discuss/154613/C%2B%2BJavaPython-Binary-Search
    1. Get gcd (greatest common divisor) and lcm (least common multiple) of (A, B).
      (a, b) = (A, B) while b > 0: (a, b) = (b, a % b)
      then, gcd = a and lcm = A * B / a
    2. How many magic numbers <= x ?
      By inclusion exclusion principle, we have:
      x / A + x / B - x / lcm
    3. Set our binary search range
      Lower bound is min(A, B), I just set left = 2.
      Upper bound is N * min(A, B), I just set right = 10 ^ 14.
    4. binary search, find the smallest x that x / A + x / B - x / lcm = N
        public int nthMagicalNumber(int N, int A, int B) {
            long a = A, b = B, tmp, l = 2, r = (long)1e14, mod = (long)1e9 + 7;
            while (b > 0) {
                tmp = a;
                a = b;
                b = tmp % b;
            }
            while (l < r) {
                long m = (l + r) / 2;
                if (m / A + m / B - m / (A * B / a) < N) l = m + 1;
                else r = m;
            }
            return (int)(l % mod);
        }
    
    Approach 2: Binary Search
    The number of magical numbers less than or equal to x is a monotone increasing function in x, so we can binary search for the answer.
    Algorithm
    Say L = \text{lcm}(A, B), the least common multiple of A and B; and let f(x) be the number of magical numbers less than or equal to x. A well known result says that L = \frac{A * B}{\text{gcd}(A, B)}, and that we can calculate the function \gcd. For more information on least common multiples and greatest common divisors, please visit Wikipedia - Lowest Common Multiple.
    Then f(x) = \lfloor \frac{x}{A} \rfloor + \lfloor \frac{x}{B} \rfloor - \lfloor \frac{x}{L} \rfloor. Why? There are \lfloor \frac{x}{A} \rfloor numbers A, 2A, 3A, \cdots that are divisible by A, there are \lfloor \frac{x}{B} \rfloor numbers divisible by B, and we need to subtract the \lfloor \frac{x}{L} \rfloor numbers divisible by A and B that we double counted.
    Finally, the answer must be between 0 and N * \max(A, B). Without loss of generality, suppose A \geq B, so that it remains to show
    \lfloor \frac{N * \max(A, B)}{A} \rfloor + \lfloor \frac{N * \max(A, B)}{B} \rfloor - \lfloor \frac{N * \max(A, B)}{\text{lcm}(A, B)} \rfloor \geq N
    \Leftrightarrow \lfloor \frac{N*A}{A} \rfloor + \lfloor \frac{N*A}{B} \rfloor - \lfloor \frac{N*A*\gcd(A, B)}{A*B} \rfloor \geq N
    \Leftrightarrow \lfloor \frac{N*A}{B} \rfloor \geq \lfloor \frac{N*\gcd(A, B)}{B} \rfloor
    \Leftrightarrow A \geq \gcd(A, B)
    as desired.
    Afterwards, the binary search on f is straightforward. For more information on binary search, please visit [LeetCode Explore - Binary Search].
    Time Complexity: O(\log (N * \max(A, B))).
      public int nthMagicalNumber(int N, int A, int B) {
        int MOD = 1_000_000_007;
        int L = A / gcd(A, B) * B;

        long lo = 0;
        long hi = (long) 1e15;
        while (lo < hi) {
          long mi = lo + (hi - lo) / 2;
          // If there are not enough magic numbers below mi...
          if (mi / A + mi / B - mi / L < N)
            lo = mi + 1;
          else
            hi = mi;
        }

        return (int) (lo % MOD);
      }

      public int gcd(int x, int y) {
        if (x == 0)
          return y;
        return gcd(y % x, x);

      }


    Approach 1: Mathematical
    Let's try to count to the N-th magical number mathematically.
    First, the pattern of magical numbers repeats. Let L be the least common multiple of A and B. If X \leq L is magical, then X + L is magical, because (for example) A | X and A | L implies A | (X + L), and similarly if Bwere the divisor.
    There are M = \frac{L}{A} + \frac{L}{B} - 1 magical numbers less than or equal to L\frac{L}{A} of them are divisible by A\frac{L}{B} of them are divisible by B, and 1 of them is divisible by both. So instead of counting one at a time, we can count by M at a time.
    Now, suppose N = M*q + r (with r < M). The first L*q numbers contain M*q magical numbers, and within the next numbers (L*q + 1, L*q + 2, \cdots) we want to find r more magical ones.
    For this task, we can use brute force. The next magical number (less L*q) will either be A or B. If for example it is A, then the next number will either be 2*A or B, and so on.
    If the r-th such magical number is Y, then the final answer is L*q + Y. Care must also be taken in the case that r is 0.
    Time Complexity: O(A+B), assuming a model where integer math operations are O(1). The calculation of q * L is O(1). The calculation of the r-th magical number after q*M is O(M) which is O(A+B).

      public int nthMagicalNumber(int N, int A, int B) {
        int MOD = 1_000_000_007;
        int L = A / gcd(A, B) * B;
        int M = L / A + L / B - 1;
        int q = N / M, r = N % M;

        long ans = (long) q * L % MOD;
        if (r == 0)
          return (int) ans;

        int[] heads = new int[] { A, B };
        for (int i = 0; i < r - 1; ++i) {
          if (heads[0] <= heads[1])
            heads[0] += A;
          else
            heads[1] += B;
        }

        ans += Math.min(heads[0], heads[1]);
        return (int) (ans % MOD);
      }

      public int gcd(int x, int y) {
        if (x == 0)
          return y;
        return gcd(y % x, x);
      }

    I have seen most of the solutions contain binary search or brute force search. Actually we can solve it with pure math solution.
    First we need to find count of the LCM blocks as each LCM block contains fixed number of magic numbers. For the remain part, instead of using brute force or binary search. Actually there's linear relationship between count of magic number (F(x)) and the number (x) in following formular:
    f(x) = floor(x/A) + floor(x/B)
    As within an LCD block, there's no overlapping between x/A and x/B.
    If we plot this in a chart, it's very close to linear furmular:
    f(x) = x/A + x/B.
    But it's always below this line.
    following is the chart exmple for A=3, B=5
    image
    So solution to get the number within an LCM block is very simple:
    The minimum integer number great than: N / ( 1/A + 1 /B ), that can be divided either by A or B.
    import static java.lang.Math.ceil;
    import static java.lang.Math.min;
    
    class Solution {
     public int nthMagicalNumber(int N, int A, int B) {
        int MOD = 1_000_000_007;
        long lcm = A * B / gcd(A, B);
        long cntPerLcm = lcm / A + lcm / B - 1;
        long cntLcm = N / cntPerLcm;
        long remain = N % cntPerLcm;
        
        double nearest = remain / (1./A + 1./B);
        int remainIdx = (int)min(ceil(nearest / A) * A, ceil(nearest / B) * B);
        return (int)((cntLcm * lcm + remainIdx) % MOD);
      }
      
      public static int gcd(int A, int B) {
        return B == 0 ? A : gcd(B, A % B);
      }
    

    Approach 1: Mathematical
    https://zihan.me/2018/08/03/LeetCode-878-Nth-Magic-Number/
    假设两个数的最小公倍数$L = LCM(A, B)$, 那么对于[1, L]这个区间里的数, 能被A整除的有$\lfloor \frac{L}{A} \rfloor$, 能被B整数的有$\lfloor \frac{L}{B} \rfloor$, 这两部分除了L之外不会有重合的, 因为L是最小公倍数, 因此在$[1, L]$这个区间内有$M = \lfloor \frac{L}{A} \rfloor + \lfloor \frac{L}{B} \rfloor - 1$。
    而$[L + 1, 2L]$这段区间中的magic number数目和第一段区间是一样的, 所以至少有$\lfloor \frac{min(A, B)}{L} \rfloor$个 magic number。但是最后还有一小段余数, 这一段直接暴力一个一个判断就好了, 反正最多不超过L个。

    代码如下, gcd可以用辗转相除法得到, 然后$lcm = \frac{A \times B}{gcd(A, B)}$
    Let's try to count to the N-th magical number mathematically.
    First, the pattern of magical numbers repeats. Let L be the least common multiple of A and B. If X \leq Lis magical, then X + L is magical, because (for example) A \| X and A \| L implies A \| (X + L), and similarly if B were the divisor.
    There are M = \frac{L}{A} + \frac{L}{B} - 1 magical numbers less than or equal to L\frac{L}{A} of them are divisible by A\frac{L}{B} of them are divisible by B, and 1 of them is divisible by both. So instead of counting one at a time, we can count by M at a time.
    Now, suppose N = M*q + r (with r &lt; M). The first L*q numbers contain M*q magical numbers, and within the next numbers (L*q + 1, L*q + 2, \cdots) we want to find r more magical ones.
    For this task, we can use brute force. The next magical number (less L*q) will either be A or B. If for example it is A, then the next number will either be 2*A or B, and so on.
    If the r-th such magical number is Y, then the final answer is L*q + Y. Care must also be taken in the case that r is 0.
    Time Complexity: O(A+B), assuming a model where integer math operations are O(1). The calculation of q * L is O(1). The calculation of the r-th magical number after q*M is O(M)which is O(A+B).

      public int nthMagicalNumber(int N, int A, int B) {
        int MOD = 1_000_000_007;
        int L = A / gcd(A, B) * B;
        int M = L / A + L / B - 1;
        int q = N / M, r = N % M;

        long ans = (long) q * L % MOD;
        if (r == 0)
          return (int) ans;

        int[] heads = new int[] { A, B };
        for (int i = 0; i < r - 1; ++i) {
          if (heads[0] <= heads[1])
            heads[0] += A;
          else
            heads[1] += B;
        }

        ans += Math.min(heads[0], heads[1]);
        return (int) (ans % MOD);
      }

      public int gcd(int x, int y) {
        if (x == 0)
          return y;
        return gcd(y % x, x);
      }


    Labels

    LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

    Popular Posts