1、题目:
请你帮忙设计一个程序,用来找出第 n 个丑数。丑数是可以被 a 或 b 或 c 整除的 正整数。
示例 :
输入:n = 3, a = 2, b = 3, c = 5
输出:4
解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。
提示:1 <= n, a, b, c <= 10^9
2、思路:
首先,为什么第一时间能想到二分法?
让我们观察题目,可以看到,最终状态(即n)的范围非常大。试图自底向上递推或是按照通常的自顶向下回溯显然会超时(比如动态规划、DFS等方法),面对这么大的状态空间,二分法的时间复杂度是logN,因此能够大大压缩需要遍历的状态数目。
sum(情况) = X/a + X/b + X/c - X/MCM_a_b - X/MCM_a_c - X/MCM_b_c + X/MCM_a_b_c,用MCM+下标表示最小公倍数。
具体思路不抄袭,贴出原文链接:Alfeim解题思路
3、C++代码:
注意最大公约数、最小公倍数算法
class Solution {
public:
using LL = long long; // 使用using定义类型别名
int nthUglyNumber(int n, int a, int b, int c) {
LL start = min(min(a,b), c);
LL end = start*n;
LL MCM_a_b = LCM(a,b); //求最小公倍数
LL MCM_a_c = LCM(a,c);
LL MCM_b_c = LCM(b,c);
LL MCM_a_b_c = LCM(MCM_a_b,c);
LL res = Binary_Search( start, end, a, b, c, n, MCM_a_b, MCM_a_c, MCM_b_c, MCM_a_b_c );
return res - min(min(res%a, res%b),res%c);
}
// 二分查找
LL Binary_Search(LL start, LL end, int a, int b, int c, int n, LL MCM_a_b, LL MCM_a_c, LL MCM_b_c, LL MCM_a_b_c) {
if(start >= end) return start;
LL mid = (end+start) >> 1; //移位代替除以2
LL res = mid/a + mid/b + mid/c - mid/MCM_a_b - mid/MCM_a_c - mid/MCM_b_c + mid/MCM_a_b_c;
if(res == n)
return mid;
else if (res<n) {
return Binary_Search( mid+1, end, a, b, c, n, MCM_a_b, MCM_a_c, MCM_b_c, MCM_a_b_c );
}
else
return Binary_Search( start, mid-1, a, b, c, n, MCM_a_b, MCM_a_c, MCM_b_c, MCM_a_b_c );
}
LL LCM(LL a, LL b) {
// 求最小公倍数
LL multi = a * b;
while(b > 0){
//最小公倍数 = a*b/(a和b的最大公约数),最大公约数可以通过辗转相除法得到。
LL tmp = a % b;
a = b;
b = tmp;
} //循环结束后a为最大公约数
LL res = multi / a;
return res;
}
};
参考资料:
题目来源:力扣(LeetCode)
题目链接:https://leetcode-cn.com/problems/ugly-number-iii
解题作者:Alfeim
解题链接:https://leetcode-cn.com/problems/ugly-number-iii/solution/er-fen-fa-si-lu-pou-xi-by-alfeim/
总结:
1、最大公约数通过辗转相除法得到。
2、最小公倍数 = a*b/(a和b的最大公约数)。