leet code 1201. Ugly Number III.

Write a program to find the n-th ugly number.

Ugly numbers are positive integers which are divisible by a or b or c.

Example 1:

Input: n = 3, a = 2, b = 3, c = 5
Output: 4
Explanation: The ugly numbers are 2, 3, 4, 5, 6, 8, 9, 10... The 3rd is 4.

Example 2:

Input: n = 4, a = 2, b = 3, c = 4
Output: 6
Explanation: The ugly numbers are 2, 3, 4, 6, 8, 9, 12... The 4th is 6.

Example 3:

Input: n = 5, a = 2, b = 11, c = 13
Output: 10
Explanation: The ugly numbers are 2, 4, 6, 8, 10, 11, 12, 13... The 5th is 10.

Example 4:

Input: n = 1000000000, a = 2, b = 217983653, c = 336916467
Output: 1999999984
参考:
[1] leetcode discuss: https://leetcode.com/problems/ugly-number-iii/discuss/387539/cpp-Binary-Search-with-picture-and-Binary-Search-Template
[2] 欧几里得算法求最大公约数:https://www.cnblogs.com/kirito-c/p/6910912.html (给出的gcd代码有错误)
题目为:求能被a或b或c整除的第n小的正整数。
翻译:求argmin_N F(N),F(N)= 能被a或b或c整除的,小于N的,数的个数,F(N) == n
进一步,令f(a)=能被a整除的小于N的数的个数,类似的f(b),f(c)。令g(a,b)为a,b的最小公倍数。
则题目变为:argmin_N F(N), F(N) = f(a) + f(b) + f(c) - f(g(a,b)) - f(g(a,c)) - f(g(b,c)) + f(g(a,b,c)), F(N) == n
盗一张图来解释:a = f(a),b=f(b)等等.

 此时涉及到两个问题:

1. 怎么求小于N的能被a整除的数的个数f(a)?

2. 怎么求a和b的最小公倍数g(a,b)?

答: 1. N/a,所有能被a整除的数可以表示为p * a,p为正整数。则所有小于N且能表示为p*a的数的个数就是f(a)的值,而p的值可以从1到N/a。所以答案为N/a

2. 最小公倍数计算方式为 a * b / a和b的最大公约数。 最大公约数可以由欧几里得算法,也就是辗转相除法解决。

大致理解为:如果有较大数a和较小数b,如果a%b==0,则b就是a和b的最大公约数。

如果a%b!=0,假设a = q * b + r,r为余数,且a = s * t,b = u * t,t为a和b的约数,则有r = (s - q * u) * t,表明t也是a和b的余数的约数。所以b和r的最大公约数就是a和b的最大公约数。这样递归,在某次余数为0的时候,除数就是最大公约数。

代码可以写为:

    int gcd(int a, int b){
        int r = 0;
        while (b){
            r = a % b;
            a = b;
            b = r;
        }
        return a;
    }

在得到f和g的计算方式后,就可以寻找满足F(N) == n 的最小的N了。可以看出F(N)是一个单调函数,即如果有M>=N,则有F(M)>=F(N)。

这类似于在有序数组里寻找值为n的元素的索引。F(N)就是一个数组,N是它的索引。因此,可以使用二分搜索。

insomniacat 提供了二分搜索的基本框架:

while(lo < hi) {
int mid = lo + (hi - lo) / 2;
if(Special condition passed)(optional):
	return mid;
if(condition passed)
  hi = mid;
else
  lo = mid + 1;
}
return lo;

最终代码:

class Solution {
public:
    int nthUglyNumber(int n, int a, int b, int c) {
        int lo = 1, hi = 2 * (int)1e9;
        long A = a, B = b, C = c;
        long AB = A * B / gcd(A, B);
        long AC = A * C / gcd(A, C);
        long BC = B * C / gcd(B, C);
        long ABC = A * BC / gcd(A, BC);
        while (lo < hi){
            int mid = (hi - lo) / 2 + lo;
            // compute F(N)
            long FN = mid / A + mid / B + mid / C - mid / AB - mid / AC - mid / BC + mid / ABC;
            if (FN < n)
                lo = mid + 1;
            else
                hi = mid;
        }
        return lo;
    }

    int gcd(long a, long b){
        int r;
        while (b){
            r = a % b;
            a = b;
            b = r;
        }
        return a;
    }
};

  

01-23 15:51