1. 程式人生 > >LeetCode204題:計數質數

LeetCode204題:計數質數

這道題真的是解法很多,但滿足時間複雜度的還不太容易想。

解法一:暴力法

外層迴圈從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時四個方法的用時比較(除了第一種暴力演算法):