class Solution {
public int nthUglyNumber(int n, int a, int b, int c) {
if (a == 1 || b == 1 || c == 1) return n;
long ab = lcm(a, b);
long ac = lcm(a, c);
long bc = lcm(b, c);
long abc = lcm(ab, c);
long left = Math.min(Math.min(a,b),c);
long right = (long)Math.pow(left,n);// 上边界是这个最小者的n倍
while (left <= right) {
long mid = left + (right - left) / 2;
long count = mid / a + mid / b + mid / c - mid / ab - mid / ac - mid / bc + mid / abc;
if (count == n) {
left = mid;
break;
} else if (count > n) {
right = mid - 1;
} else {
left = mid + 1;
}
}
// 比如第n个丑数是X,那么[X,X + min(a,b,c))这个半开区间内的所有数都同时包含n个丑数因子,我们通过二分法得到的答案也随机分布于这个区间中。而实际上我们只需要得到该区间的左端即可。处理方法很简单:假设我们得到的临时答案是K(K∈[X,X + min(a,b,c))),那么K - min(K%a,K%b,K%c) = X.也就是只需要把临时答案减去其与a、b、c三者中取余的最小值即可!
return (int)(left - Math.min(Math.min(left % a,left % b),left % c));
}
// 最小公倍数
private long lcm(long a, long b) {
// 最小公倍数等于两个数相乘除以最大公约数
return a * b / gcd(a, b);
}
// 最大公约数
private long gcd (long a, long b) {
if (a == 0) return b;
return gcd(b % a, a);
}
}