这道题真的是解法很多,但满足时间复杂度的还不太容易想。
解法一:暴力法
外层循环从2到n,内层循环从2到ni,然后ni取模内层循环的值,判断是否为质数。复杂度O(n2),舍弃。
解法二:根据之前的质数求
思路:一个数如果与比它小的所有的质数取模后结果都不为0时,那么此数也是质数。所以,从2开始遍历到n时,每遇到一个质数就将其放到ArrayList中(用ArrayList而不用treeset,因为不需要多余的排序操作)。每次判断一个数是不是质数时,就遍历ArrayList进行取模比较。
虽然比较的次数少了很多了,但遗憾的是依然没通过测试用例。
public static int countPrimes(int n) {
if(n < 3)
return 0;
if(n == 3)
return 1;
List<Integer> set=new ArrayList<Integer>();
set.add(2);
int flag = 1;
for(int i=3;i<n;i++){
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
if(i%it.next()==0){
flag = -1;
break;
}
}
if(flag == 1){
set.add(i);
}
flag = 1;
}
return set.size();
}
解法三:质数折半比较
在解法二中,每次都比较了比当前值小的所有质数。但观察后可以发现,一个数如果与比它小的所有质数的前一半取模都不为0的话,那么就没必要与后一半进行比较了,因为除以小的数得到的数就是大的,所有如果除小的除不尽,那么除大的肯定除不尽。这样又能减少比较次数。
遗憾的是,依然没通过全部的测试用例。好像是卡在1500000了。
public static int countPrimes(int n) {
if(n < 3)
return 0;
if(n == 3)
return 1;
List<Integer> set=new ArrayList<Integer>();
set.add(2);
int flag = 1;
for(int i=3;i<n;i++){
Iterator<Integer> it = set.iterator();
int mid = set.size();
int count = 0;//增加count变量用来折半
while(it.hasNext()&&count<=mid/2){
if(i%it.next()==0){
flag = -1;
break;
}
count++;
}
if(flag == 1){
set.add(i);
}
flag = 1;
}
return set.size();
}
解法四:根号n比较
解法二中采用的比较前一半的质数的方法复杂度还是不符合要求,那么考虑将n取根号后取整,然后再与ArrayList中小于(int)Math.sqrt(n)的质数进行取模比较,思想其实与解法二是一样的,都是基于除以小的数会得到大的数,而一个数想要被整除并且余数大于除数的话,除数最大就是sqrt(n),而解法二采用的是质数折半,并不如这种精确。这样就能进一步减少比较次数。
并且,这种方法通过了所有测试用例。
public static int countPrimes(int n) {
if(n < 3)
return 0;
if(n == 3)
return 1;
List<Integer> set=new ArrayList<Integer>();
set.add(2);
int flag = 1;
for(int i=3;i<n;i++){
Iterator<Integer> it = set.iterator();
int mid = (int)Math.sqrt(n);
while(it.hasNext()){
int next = it.next();
if(next<=mid){
if(i%next==0){
flag = -1;
break;
}
}else{
break;
}
}
if(flag == 1){
set.add(i);
}
flag = 1;
}
return set.size();
}
解法五:埃拉托色尼素数筛法
这种算法的思想是,先假定所有的数都是质数,并以布尔型数组存放(质数用true表示)。然后从2开始迭代,因为默认的都是质数,所以2是质数,那么2的平方及2的平方+2m一定都不是质数,所以把对应不是质数的元素改为false。然后迭代到3,由于默认的是质数,并且之前的2的循环并没有修改3为false,所以3依然是质数,那么同理,3的平方及3的平方+3m也一定都是非质数,故修改为true。以此迭代。
下面的代码是摘抄的网上的。
public static int countPrimes(int n) { //暴力算法是过不了的qwq
//这个要用筛法实现
boolean[] isPrime = new boolean[n];
int result = 0;
for (int i = 2; i < n; i++) {
isPrime[i] = true; //先初始化为true
}
for (int i = 2; i * i < n; i++) { //这一次for循环找出所有不是素数的数(也就是说被筛掉了)
if (!isPrime[i]) {
//既然已经被筛掉了就不用管了
continue;
}
else {
for (int j = i * i; j < n; j += i) {
//由于i现在是一个素数, 那么i的平方一定不是素数,i^2 + i; i^2 + 2i也一定不是素数
isPrime[j] = false;
}
}
} //所有不是素数的数已经全部筛掉
//计算剩余的素数个数
for (int i = 2; i < n; i++) {
if (isPrime[i] == true) {
result++;
}
}
return result;
}
下面是n等于100000时四个方法的用时比较(除了第一种暴力算法):