1 质数
计算质数:统计所有小于非负整数 n 的质数的数量。
1 暴力
判断素数的常规方法是判断这个数是否只能被1和自身整除,常规方法是从1-n遍历,利用自己编写的isprime(i)函数查看这个数是否为素数,时间复杂度位
O
(
n
2
)
O(n^2)
O(n2)
在isprime(i)函数函数中,只需遍历到sqrt(n)即可。也就是说,如果在 [2,sqrt(n)] 这个区间之内没有发现可整除因子,就可以直接断定 n 是素数了,因为在区间 [sqrt(n),n] 也一定不会发现可整除因子。因为当n=12时,
2*6=12
6*2=12
bool isPrime(int x) {
if (x<2) return false;
for (int i=2;i*i<=x;i++)
if (x%i==0) return false;
return true;
}
int countPrimes(int n) {
int count=0;
for (int i=2;i<n;i++)
if (isPrime(i)) count++;
return count;
}
2 厄拉多塞筛法
简便方法是:先开辟一个1xn的数组,假设从1-(n-1)的数都是素数,记为true,反过来求出不是素数的的元素,记为false,最后只需遍历数组中true的个数即为质数的个数
判断不是素数的方法是: 2是素数,则 2*2 2*3 2*4不是素数
3是素数,则 3*2 3*3 3*4不是素数
4不是素数,已经被2更新过
5是素数,则 5*2 5*3 5*4不是素数 … 。
int countPrimes(int n) {
if(n<=1)
return false;
vector <bool> v(n,true);
v[0]=v[1]=false;
for(int i=1; i<n;i++)
{
if(v[i]==true)
{
int j=i,k=2;
while(j<n)
{
j=i*(k++);
if(j<n)
v[j]=false;
}
}
}
int count=0;
for(auto e:v)
{
if(e==true)
count++;
}
return count;
我们还可以继续优化,当我们知道
n=24
3*12 =24 2是质数,则12肯定不是质数。
3*8 =24 3是质数,则8肯定不是质数。
将for循环改成如下:
for(int i=1; i*i<n;i++)
2 丑数
如果只是让我们判断丑数,则和leetcode 263.题一样。
编写一个程序判断给定的数是否为丑数。
丑数就是只包含质因数 2, 3, 5 的正整数。1是丑数。
思路:我们知道,任何一个数都可以写成如下形式:
n
u
m
=
p
1
k
1
p
2
k
2
p
3
k
3
p
4
k
4
.
.
.
num=p^{k_1}_1p^{k_2}_2p^{k_3}_3p^{k_4}_4...
num=p1k1p2k2p3k3p4k4...
其中
p
1
,
p
2
.
.
.
p_1,p_2...
p1,p2...是素数,
k
1
,
k
2
.
.
.
k_1,k_2...
k1,k2...是任意数。
则我们得到丑数可以表示为
n
u
m
=
2
k
1
3
k
2
5
k
3
num=2^{k_1}3^{k_2}5^{k_3}
num=2k13k25k3,所以当我们判断一个数是否为丑数时,只需不停的整除2 3 5,如果最后结果1,则是丑数,否则返回false
bool isUgly(int num) {
//不停的整除2 3 5,如果最后结果1,则是丑数,否则返回false
if(num<=0)
return false;
if(num==1)
return true;
while(num%2==0) num/=2;
while(num%3==0) num/=3;
while(num%5==0) num/=5;
if(num!=1)
return false;
else
return true;
1 暴力
而当我们把题目变为编写一个程序,找出第 n 个丑数。暴力解法就是循环判断,这在leetcode会超时。
bool isugly(int num)
{
if(num<=1)
return true;
while(num%2==0) num/=2;
while(num%3==0) num/=3;
while(num%5==0) num/=5;
if(num==1)
return true;
return false;
}
int nthUglyNumber(int n) {
int num=1,flag=0;
while(1)
{
if(isugly(num++))
flag++;
if(flag==n)
break;
}
return --num;
}
暴力方法也可以是列举出[1-INT_MAX]中所有的丑数,然后返回v[n-1]。
int nthUglyNumber(int n) {
vector<int> v;
for(long i=1; i<=INT_MAX;i*=2)
{
for(long j=i; j<=INT_MAX;j*=3)
{
for(long k=j; k<=INT_MAX;k*=5)
v.push_back(k);
}
}
sort(v.begin(),v.end());
return v[n-1];
}
这个方法没有重复元素,所以不要去重。
2 堆
利用优先队列有自动排序的功能
每次取出队头元素,存入队头元素*2、队头元素*3、队头元素*5。但注意,像12这个元素,可由4乘3得到,也可由6乘2得到,所以要注意去重
int nthUglyNumber(int n) {
//建立一个小根堆,向堆里插入元素。
priority_queue<double,vector<double>,greater<double>> pq;
pq.push(1);
int ans=1;
for(int i=1;i<n;i++)
{
pq.push(ans*2);
pq.push(ans*3);
pq.push(ans*5);
ans=pq.top();
pq.pop();
while(!pq.empty()&&pq.top()==ans)
pq.pop();
}
return ans;
}
3 动 规
我们所有的丑数都是
n
u
m
=
2
k
1
3
k
2
5
k
3
num=2^{k_1}3^{k_2}5^{k_3}
num=2k13k25k3, 通过之前的丑数乘以 2, 3, 5 生成的,所以丑数序列可以看成下边的样子。
1, 1×2, 1×3, 2×2, 1×5, 2×3, 2×4, 3×3…。
我们可以把丑数分成三组,用丑数序列分别乘 2, 3, 5 。
乘 2: 1×2, 2×2, 3×2, 4×2, 5×2, 6×2, 8×2,9×2,…
乘 3: 1×3, 2×3, 3×3, 4×3, 5×3, 6×3, 8×3,9×3,…
乘 5: 1×5, 2×5, 3×5, 4×5, 5×5, 6×5, 8×5,9×5,…
我们需要做的就是把上边三组按照顺序合并起来。
合并有序数组的话,可以通过归并排序的思想,利用三个指针,每次找到三组中最小的元素,然后指针后移。
当然,最初我们我们并不知道丑数序列,我们可以一边更新丑数序列,一边使用丑数序列。状态转移方程为:
d
p
[
i
]
=
m
i
n
(
m
i
n
(
x
1
,
x
2
)
,
x
3
)
dp[i]=min(min(x1,x2),x3)
dp[i]=min(min(x1,x2),x3)
=
m
i
n
(
m
i
n
(
2
∗
d
p
[
i
n
d
e
x
2
]
,
3
∗
d
p
[
i
n
d
e
x
3
]
)
,
5
∗
d
p
[
i
n
d
e
x
5
]
)
=min(min(2*dp[index2],3*dp[index3]),5*dp[index5])
=min(min(2∗dp[index2],3∗dp[index3]),5∗dp[index5])
int nthUglyNumber(int n) {
//归并排序
vector<int> dp(n,1);
int index2=0,index3=0,index5=0;
for(int i=1;i<n;i++)
{
int x1=2*dp[index2];
int x2=3*dp[index3];
int x3=5*dp[index5];
int ele=min(min(x1,x2),x3);
dp[i]=ele;
if(ele==x1)
index2++;
if(ele==x2)
index3++;
if(ele==x3)
index5++;
}
return dp[n-1];
}