编写一个程序,找出第 n 个丑数。
丑数就是只包含质因数 2, 3, 5 的正整数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
说明:
1 是丑数。
n 不超过1690。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/ugly-number-ii
1.暴力(brute force)
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> v;
for (long long a=1;a<=INT_MAX;a=a*2)
for (long long b=a;b<=INT_MAX;b=b*3)
for (long long c=b;c<=INT_MAX;c=c*5)
v.push_back(c);
sort(v.begin(),v.end());
return v.at(n-1);
}
};
2.优先队列(小顶堆)
c++优先队列(priority_queue)用法详解
https://blog.csdn.net/weixin_36888577/article/details/79937886
既然是队列那么先要包含头文件#include
, 他和queue
不同的就在于我们可以自定义其中数据的优先级, 让优先级高的排在队列前面,优先出队
优先队列具有队列的所有特性,包括基本操作,只是在这基础上添加了内部的一个排序,它本质是一个堆实现的
和队列基本操作相同:
- top 访问队头元素
- empty 队列是否为空
- size 返回队列内元素个数
- push 插入元素到队尾 (并排序)
- emplace 原地构造一个元素并插入队列
- pop 弹出队头元素
- swap 交换内容
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型,Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式,当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
一般是:
//升序队列
priority_queue <int,vector<int>,greater<int> > q;
//降序队列
priority_queue <int,vector<int>,less<int> >q;
//greater和less是std实现的两个仿函数(就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了)
基本类型例子:
#include<iostream>
#include <queue>
using namespace std;
int main()
{
//对于基础类型 默认是大顶堆
priority_queue<int> a;
//等同于 priority_queue<int, vector<int>, less<int> > a;
// 这里一定要有空格,不然成了右移运算符↓
priority_queue<int, vector<int>, greater<int> > c; //这样就是小顶堆
priority_queue<string> b;
for (int i = 0; i < 5; i++)
{
a.push(i);
c.push(i);
}
while (!a.empty())
{
cout << a.top() << ' ';
a.pop();
}
cout << endl;
while (!c.empty())
{
cout << c.top() << ' ';
c.pop();
}
cout << endl;
b.push("abc");
b.push("abcd");
b.push("cbd");
while (!b.empty())
{
cout << b.top() << ' ';
b.pop();
}
cout << endl;
return 0;
}
输出
输出
4 3 2 1 0
0 1 2 3 4
cbd abcd abc
2.pari的比较,先比较第一个元素,第一个相等比较第二个
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '
';
a.pop();
}
}
输出
2 5
1 3
1 2
3.对于自定义类型
#include <iostream>
#include <queue>
using namespace std;
//方法1
struct tmp1 //运算符重载<
{
int x;
tmp1(int a) {x = a;}
bool operator<(const tmp1& a) const
{
return x < a.x; //大顶堆
}
};
//方法2
struct tmp2 //重写仿函数
{
bool operator() (tmp1 a, tmp1 b)
{
return a.x < b.x; //大顶堆
}
};
int main()
{
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
while (!d.empty())
{
cout << d.top().x << '
';
d.pop();
}
cout << endl;
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(c);
f.push(b);
f.push(a);
while (!f.empty())
{
cout << f.top().x << '
';
f.pop();
}
}
输出
3
2
1
3
2
1
利用优先队列有自动排序的功能
每次取出队头元素,存入队头元素2、队头元素3、队头元素5
但注意,像12这个元素,可由4乘3得到,也可由6乘2得到,所以要注意去重
class Solution {
public:
int nthUglyNumber(int n) {
priority_queue <double,vector<double>,greater<double> > q;
double answer=1;
for (int i=1;i<n;++i)
{
q.push(answer*2);
q.push(answer*3);
q.push(answer*5);
answer=q.top();
q.pop();
while (!q.empty() && answer==q.top())
q.pop();
}
return answer;
}
};
还可以更进一步采用set来识别有无重复
class Solution {
public:
int nthUglyNumber(int n) {
priority_queue <double,vector<double>,greater<double> > q;
set<int> s;
s.insert(1);
vector<int> mask({2,3,5});
double answer=1;
for (int i=1;i<n;++i)
{
for (int &j:mask)
if (s.count(answer*j)==0)
{
q.push(answer*j);
s.insert(answer*j);
}
answer=q.top();
q.pop();
}
return answer;
}
};
3.动态规划(三指针)
我们先模拟手写丑数的过程
1打头,1乘2 1乘3 1乘5,现在是{1,2,3,5}
轮到2,2乘2 2乘3 2乘5,现在是{1,2,3,4,5,6,10}
手写的过程和采用小顶堆的方法很像,但是怎么做到提前排序呢
小顶堆的方法是先存再排,dp的方法则是先排再存
我们设3个指针p_2,p_3,p_5
代表的是第几个数的2倍、第几个数3倍、第几个数5倍
动态方程dp[i]=min(dp[p_2]2,dp[p_3]3,dp[p_5]*5)
小顶堆是一个元素出来然后存3个元素
动态规划则是标识3个元素,通过比较他们的2倍、3倍、5倍的大小,来一个一个存
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> dp(n);
dp.at(0)=1;
int p_2,p_3,p_5;
p_2=p_3=p_5=0;
for (int i=1;i<n;++i)
{
dp.at(i)=min(min(2*dp.at(p_2),3*dp.at(p_3)),5*dp.at(p_5));
if (dp.at(i)==2*dp.at(p_2))
++p_2;
if (dp.at(i)==3*dp.at(p_3))
++p_3;
if (dp.at(i)==5*dp.at(p_5))
++p_5;
}
return dp.at(n-1);
}
};
Java
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
for (int i = 1; i < n; i++) {
int min = Math.min(dp[i2] * 2, Math.min(dp[i3] * 3, dp[i5] * 5));
if (min == dp[i2] * 2) i2++;
if (min == dp[i3] * 3) i3++;
if (min == dp[i5] * 5) i5++;
dp[i] = min;
}
return dp[n - 1];
}
C++
这道题是之前那道 Ugly Number 的拓展,这里让找到第n个丑陋数,还好题目中给了很多提示,基本上相当于告诉我们解法了,根据提示中的信息,丑陋数序列可以拆分为下面3个子列表:
(1) 1x2, 2x2, 2x2, 3x2, 3x2, 4x2, 5x2...
(2) 1x3, 1x3, 2x3, 2x3, 2x3, 3x3, 3x3...
(3) 1x5, 1x5, 1x5, 1x5, 2x5, 2x5, 2x5...
仔细观察上述三个列表,可以发现每个子列表都是一个丑陋数分别乘以 2,3,5,而要求的丑陋数就是从已经生成的序列中取出来的,每次都从三个列表中取出当前最小的那个加入序列,请参见代码如下:
解法一:
class Solution {
public:
int nthUglyNumber(int n) {
vector<int> res(1, 1);
int i2 = 0, i3 = 0, i5 = 0;
while (res.size() < n) {
int m2 = res[i2] * 2, m3 = res[i3] * 3, m5 = res[i5] * 5;
int mn = min(m2, min(m3, m5));
if (mn == m2) ++i2;
if (mn == m3) ++i3;
if (mn == m5) ++i5;
res.push_back(mn);
}
return res.back();
}
};
我们也可以使用最小堆来做,首先放进去一个1,然后从1遍历到n,每次取出堆顶元素,为了确保没有重复数字,进行一次 while 循环,将此时和堆顶元素相同的都取出来,然后分别将这个取出的数字乘以 2,3,5,并分别加入最小堆。这样最终 for 循环退出后,堆顶元素就是所求的第n个丑陋数,参见代码如下:
解法二:
class Solution {
public:
int nthUglyNumber(int n) {
priority_queue<long, vector<long>, greater<long>> pq;
pq.push(1);
for (long i = 1; i < n; ++i) {
long t = pq.top(); pq.pop();
while (!pq.empty() && pq.top() == t) {
t = pq.top(); pq.pop();
}
pq.push(t * 2);
pq.push(t * 3);
pq.push(t * 5);
}
return pq.top();
}
};
,