LC 264. 丑数 II
题目:你一个整数 n ,请你找出并返回第 n 个 丑数 。丑数 就是只包含质因数 2、3 和/或 5 的正整数。
方法:介绍三种方法,从最直观的开始
方法一:堆+欧拉筛
每次从堆中取一个最小的,然后分别乘2,3,5进行扩展。每次取出的最小值加入答案,需要去重。
有大量的重复,需要欧拉筛优化才能过
class Solution {
public:
typedef long long ll;
int nthUglyNumber(int n) {
vector<ll>primes = {2, 3, 5};
priority_queue<ll, vector<ll>, greater<ll>>pq;
pq.push(1);
set<ll>res;
while(res.size() < n) {
ll p = pq.top();pq.pop();
res.insert(p);
for(int prime : primes) {
pq.push(prime * p);
if(p % prime == 0) break; // 必需的优化,欧拉筛,p是由prime扩展来的
}
}
return *(--res.end()); // 集合中的最后一个
}
};
方法二:二分
考虑第i个元素是如何生成的,肯定是从已经生成的元素中找到一个值,其乘以2/3/5的值大于且最接近第i-1个。
对于2,我们可以从第一个开始枚举,直到超过第i-1元素,3、5同理;
然后取三者的最小值
由于已生成的数组是有序的,因此可以二分查找
class Solution {
public:
int nthUglyNumber(int n) {
vector<int>dp;
dp.push_back(1);
for(int i = 1;i < n;i++) {
int a = *upper_bound(dp.begin(), dp.end(), dp[dp.size()-1]/2) * 2;
int b = *upper_bound(dp.begin(), dp.end(), dp[dp.size()-1]/3) * 3;
int c = *upper_bound(dp.begin(), dp.end(), dp[dp.size()-1]/5) * 5;
dp.push_back(min(a, min(b, c)));
}
return dp[n-1];
}
};
方法三:三指针
我们从方法二中得到启发,可以发现,二分查找到的点总是往右移的,且如果未采用这个点的值说明该点还未被使用,点不用右移;使用的就右移一位。
class Solution {
public:
int nthUglyNumber(int n) {
int i2 = 0, i3 = 0, i5 = 0;
vector<int>dp(n, 0);
dp[0] = 1;
for(int i = 1;i < n;i++) {
dp[i] = min(dp[i2]*2, min(dp[i3]*3, dp[i5]*5));
if(dp[i] == dp[i2]*2) i2++;
if(dp[i] == dp[i3]*3) i3++; // 不能用else if,为了去重
if(dp[i] == dp[i5]*5) i5++;
}
return dp[n-1];
}
};
LC 313. 超级丑数
题目:与上题相比,不只2,3,4,而是由一个primes数组
方法:堆+欧拉筛
class Solution {
public:
typedef long long ll;
int nthSuperUglyNumber(int n, vector<int>& primes) {
priority_queue<ll, vector<ll>, greater<ll>>pq;
pq.push(1);
set<ll>res;
while(res.size() < n) {
ll p = pq.top();pq.pop();
res.insert(p);
for(int prime : primes) {
pq.push(prime * p);
if(p % prime == 0) break; // 欧拉筛,p是由prime扩展来的
}
}
return *(--res.end()); // 集合中的最后一个
}
};
当然,也可以使用上面两种方法。