LeetCode204题:计数质数

版权声明:本文为博主原创文章,转载请注明出处! https://blog.csdn.net/ASN_forever/article/details/84948104

这道题真的是解法很多,但满足时间复杂度的还不太容易想。

解法一:暴力法

外层循环从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时四个方法的用时比较(除了第一种暴力算法):

猜你喜欢

转载自blog.csdn.net/ASN_forever/article/details/84948104
今日推荐