如果题目只需要判断少量数字是否为素数,那么可以直接枚举因子从2一直到sqrt(n),看能否整除,当判断的数量较大的,需要使用筛法进行处理。
筛法求素数就是先认定所有的数是素数,再通过一些方法把合数踢掉。
埃拉托斯特尼筛法:
实例代码:
class Solution {
public int countPrimes(int n) {
if(n<=2){
return 0;
}
boolean[] prime=new boolean[n];
Arrays.fill(prime,true);
for(int i=2;i*i<n;i++){
for(int j=i*i;j<n;j+=i){
prime[j]=false;
}
}
int count=0;
for(int i=2;i<n;i++){
if(prime[i]){
count++;
}
}
return count;
}
}
基本思路是,假如一个数是素数,那么这个数的倍数就是合数,这样就可以将这些合数筛掉。
通过仔细分析可以发现,这样的做法会造成重复筛合数,影响效率,比如30,在i=2的时候2 * 15判断了一次,在i=3的时候3 * 10又判断了一次,同理在i=5等位置进行了重复判断,因此便有了改进的筛法。
快速线性筛法(欧拉筛):
快速线性筛在普通筛法的基础上,消除了冗余:
class Solution {
public int countPrimes(int n) {
if(n<=2){
return 0;
}
boolean[] prime=new boolean[n];
int[] primes=new int[n];
Arrays.fill(prime,true);
int count=0;
for(int i=2;i<n;i++){
if(prime[i]){
primes[count++]=i;
}
//关键点1
for(int j=0;j<count&&i*primes[j]<n;j++){
prime[i*primes[j]]=false;
//关键点2
if(i%primes[j]==0){
break;
}
}
}
return count;
}
}
算法理解:
首先明确一个条件,任何合数都可以表示成一系列素数的乘积:
n = p 1 x 1 × p 2 x 2 × . . . × p n x n n=p_1^{x_1}\times p_2^{x_2}\times...\times p_n^{x_n} n=p1x1×p2x2×...×pnxn
从以上式子可以得到,p1是最小的因子,这样每个合数就有一个唯一的表示方法,不会重复:
最 小 的 素 数 ( p 1 ) × 其 他 ( n / p 1 ) 最小的素数(p_1) \times其他(n/p1) 最小的素数(p1)×其他(n/p1)
现在我们规定一个合数由两个数得到,那么合数有两种:
- 素 数 × 素 数 素数\times素数 素数×素数
- 一 个 最 小 的 素 数 × 合 数 一个最小的素数\times合数 一个最小的素数×合数
筛除过程:
首先,不管i是否是素数,都会执行到“关键处1”。
- 此时如果i是素数的话,一个大的素数i乘以不大于i的素数,这样筛除的数根之前的是不会重复的。筛除的数都是 N = p 1 × p 2 N=p_1\times p_2 N=p1×p2的形式, p 1 , p 2 p_1,p_2 p1,p2之间不相等。
- 如果i是合数,此时i可以表示成上面递增素数相乘的形式, p 1 p_1 p1是最小的系数,根据“关键处2”的定义,当p1==prime[j]的时候,筛除就终止了,也就是说,只能筛除不大于 p 1 p_1 p1的质数*i
下面举个例子来直观理解:
i = 2 × 3 × 5 i=2\times3\times5 i=2×3×5
此时可以筛除 2 × i 2\times i 2×i,不能筛除 3 × i 3\times i 3×i
如果可以筛除 3 × i 3\times i 3×i的话,当 i ′ i' i′等于 i ′ = 3 × 3 × 5 i'=3\times3\times5 i′=3×3×5的时候,筛除 2 × i ′ 2\times i' 2×i′就和前面重复了
练习题:
leetcode 204