丑数
丑数的定义
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。
1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
丑数的判断
依次循环除以2、3、5直到不能整除,最后值为1则为丑数。
bool isUgly(int num) {
if(num <= 0) return false;
if(num < 7) return true;
while(num % 2 == 0)
num /= 2;
while(num % 3 == 0)
num /= 3;
while(num % 5 == 0)
num /= 5;
return num == 1;
}
找出第N大的丑数
【思路】 动态规划
丑数可以看做是2、3、5的幂的积。
根据幂得到一个丑数容易,问题的难点在于如何使得数据有序。依次递增存储丑数,使用三指针记录当前乘以2、3、5待选的丑数索引,再记录待选中间结果减少计算次数。
int nthUglyNumber(int n) {
if(n < 7) return n; // 1 2 3 4 5 6
vector<int> uglyNums(n, 1); // n个丑数 第一个为1
int index_2 = 0, index_3 = 0, index_5 = 0; // 当前乘2、3、5的丑数列表中值的下标
int tmp_2 = 2, tmp_3 = 3, tmp_5 = 5; // 乘2、3、5的待选丑数(减少计算次数)
for(int i = 1; i < n; ++i){
uglyNums[i] = min(tmp_2, min(tmp_3, tmp_5)); // 取最小值
// 更新待选丑数
// 存在待选值相等的情况
if(uglyNums[i] == tmp_2){
tmp_2 = uglyNums[++index_2] * 2;
}
if(uglyNums[i] == tmp_3){
tmp_3 = uglyNums[++index_3] * 3;
}
if(uglyNums[i] == tmp_5){
tmp_5 = uglyNums[++index_5] * 5;
}
}
return uglyNums[n-1];
}
超级丑数
超级丑数是指其所有质因数都是长度为 k
的质数列表 primes
中的正整数。
【思路】 动态规划
// 得到最小待选结果索引集合
vector<int> findMinValueIndex(vector<int>& tmp, const int& k){
int minValue = tmp[0]; // 待选最小值
vector<int> minValueIndexs(1, 0);
for(int i = 1; i < k; ++i){
if(tmp[i] < minValue){
minValue = tmp[i]; // 最小值
minValueIndexs.clear(); // 擦除旧索引
minValueIndexs.push_back(i); // 新索引
}else if(tmp[i] == minValue){
minValueIndexs.push_back(i);
}
}
return minValueIndexs;
}
// 第N个超级丑数
int nthSuperUglyNumber(int n, vector<int>& primes) {
vector<int> uglyNums(n, 1); // n个丑数 第一个丑数是1
int k = primes.size();
vector<int> index(k, 0); // 索引
vector<int> tmp(primes); // 待选数据缓冲
for(int i = 1; i < n; ++i){
vector<int> tmpIndex = findMinValueIndex(tmp, k); // 最小待选值索引
uglyNums[i] = tmp[tmpIndex[0]];
for(auto& val : tmpIndex){
++index[val];
tmp[val] = uglyNums[index[val]] * primes[val];
}
}
return uglyNums[n-1];
}
丑数 III --- 二分
请你帮忙设计一个程序,用来找出第
n
个丑数。丑数是可以被
a
或b
或c
整除的 正整数。(1不算丑数)
class Solution {
/** 【二分法】
给定一个丑数 X :
1. 该数只能被a整除 (该数一定是a 的整数倍) {X/a} - {情况4 + 情况5 + 情况7}
2. 该数只能被b整除 (该数一定是b 的整数倍) {X/b} - {情况4 + 情况6 + 情况7}
3. 该数只能被c整除 (该数一定是c 的整数倍) {X/c} - {情况5 + 情况6 + 情况7}
4. 该数只能被a和b同时整除 (该数一定是a、b最小公倍数的整数倍) {X/ab} - {情况7}
5. 该数只能被a和c同时整除 (该数一定是a、c最小公倍数的整数倍) {X/ac} - {情况7}
6. 该数只能被b和c同时整除 (该数一定是b、c最小公倍数的整数倍) {X/bc} - {情况7}
7. 该数只能被a和b和c同时整除 (该数一定是a、b、c的最小公倍数的整数倍) {X/abc}
(1, X] 有多少个丑数 :
{X/a} + {X/b} + {X/c} - {X/ab} - {X/ac} - {X/bc} + {X/abc}
**/
long long ab, ac, bc, abc; // 最小公倍数
/**
二分查找第 n 个正整数丑数
**/
long long binarySearch(int& n, int& a, int& b, int& c, long long left, long long right){
if(left >= right)
return left;
long long mid = left + ((right - left) >> 1);
// 计算 (1, mid] 有多少个丑数
long long num = mid/a + mid/b + mid/c - mid/ab - mid/ac - mid/bc + mid/abc;
if(num == n)
return mid;
else if(num < n)
return binarySearch(n, a, b, c, mid+1, right);
else
return binarySearch(n, a, b, c, left, mid-1);
}
// 两数最小公倍数
// 乘积 = 最小公倍数 * 最大公约数
long long leastCommonMultiple(int a, int b){
long long mult = (long long)a * b;
// 辗转相除求最大公约数
while(b > 0){
int tmp = a % b;
a = b;
b = tmp;
}
return mult / a;
}
public:
int nthUglyNumber(int n, int a, int b, int c){
// 计算最小公倍数
ab = leastCommonMultiple(a, b);
ac = leastCommonMultiple(a, c);
bc = leastCommonMultiple(b, c);
abc = leastCommonMultiple(ab, c);
long long left = min(a, min(a, b));
long long right = left * n; // 第 n 个丑数不超过第一个丑数的 n 倍
long ans = binarySearch(n, a, b, c, left, right);
return ans - min(ans % a, min(ans % b, ans % c));
}
};