[LeetCode-204] Count Primes(0~n 有多少个质数—4种方法求解)

Description:

Count the number of prime numbers less than a non-negative number, n.

Credits:
Special thanks to @mithmatt for adding this problem and creating all test cases.

Hint:

  1. Let's start with a isPrime function. To determine if a number is prime, we need to check if it is not divisible by any number less than n. The runtime complexity of isPrimefunction would be O(n) and hence counting the total prime numbers up to n would be O(n2). Could we do better?

  2. As we know the number must not be divisible by any number > n / 2, we can immediately cut the total iterations half by dividing only up to n / 2. Could we still do better?

  3. Let's write down all of 12's factors:

    2 × 6 = 12
    3 × 4 = 12
    4 × 3 = 12
    6 × 2 = 12
    

    As you can see, calculations of 4 × 3 and 6 × 2 are not necessary. Therefore, we only need to consider factors up to √n because, if n is divisible by some number p, then n = p × q and since p ≤ q, we could derive that p ≤ √n.

    Our total runtime has now improved to O(n1.5), which is slightly better. Is there a faster approach?

    public int countPrimes(int n) {
       int count = 0;
       for (int i = 1; i < n; i++) {
          if (isPrime(i)) count++;
       }
       return count;
    }
    
    private boolean isPrime(int num) {
       if (num <= 1) return false;
       // Loop's ending condition is i * i <= num instead of i <= sqrt(num)
       // to avoid repeatedly calling an expensive function sqrt().
       for (int i = 2; i * i <= num; i++) {
          if (num % i == 0) return false;
       }
       return true;
    }
    
  4. The Sieve of Eratosthenes is one of the most efficient ways to find all prime numbers up to n. But don't let that name scare you, I promise that the concept is surprisingly simple.


    Sieve of Eratosthenes: algorithm steps for primes below 121. "Sieve of Eratosthenes Animation" by SKopp is licensed under CC BY 2.0.

    We start off with a table of n numbers. Let's look at the first number, 2. We know all multiples of 2 must not be primes, so we mark them off as non-primes. Then we look at the next number, 3. Similarly, all multiples of 3 such as 3 × 2 = 6, 3 × 3 = 9, ... must not be primes, so we mark them off as well. Now we look at the next number, 4, which was already marked off. What does this tell you? Should you mark off all multiples of 4 as well?

  5. 4 is not a prime because it is divisible by 2, which means all multiples of 4 must also be divisible by 2 and were already marked off. So we can skip 4 immediately and go to the next number, 5. Now, all multiples of 5 such as 5 × 2 = 10, 5 × 3 = 15, 5 × 4 = 20, 5 × 5 = 25, ... can be marked off. There is a slight optimization here, we do not need to start from 5 × 2 = 10. Where should we start marking off?

  6. In fact, we can mark off multiples of 5 starting at 5 × 5 = 25, because 5 × 2 = 10 was already marked off by multiple of 2, similarly 5 × 3 = 15 was already marked off by multiple of 3. Therefore, if the current number is p, we can always mark off multiples of p starting at p2, then in increments of pp2 + pp2 + 2p, ... Now what should be the terminating loop condition?

  7. It is easy to say that the terminating loop condition is p < n, which is certainly correct but not efficient. Do you still remember Hint #3?

  8. Yes, the terminating loop condition can be p < √n, as all non-primes ≥ √n must have already been marked off. When the loop terminates, all the numbers in the table that are non-marked are prime.

    The Sieve of Eratosthenes uses an extra O(n) memory and its runtime complexity is O(n log log n). For the more mathematically inclined readers, you can read more about its algorithm complexity on Wikipedia.

    public int countPrimes(int n) {
       boolean[] isPrime = new boolean[n];
       for (int i = 2; i < n; i++) {
          isPrime[i] = true;
       }
       // Loop's ending condition is i * i < n instead of i < sqrt(n)
       // to avoid repeatedly calling an expensive function sqrt().
       for (int i = 2; i * i < n; i++) {
          if (!isPrime[i]) continue;
          for (int j = i * i; j < n; j += i) {
             isPrime[j] = false;
          }
       }
       int count = 0;
       for (int i = 2; i < n; i++) {
          if (isPrime[i]) count++;
       }
       return count;
    }

    分析,题意是给定一个数值n,算出从0到n之间有多少个数是质数,质数指的是只能被1和自己本身整除的数字,1既不是质数也不是合数,我知道那个for循环,如果数值非常大的话,那个运算量会很大,而且测试了很多不可能的值,比如 n = 40,他不可能被大于 40/2 ~ 40之间的数整除吧,也就是从21到40之间的数值可以直接去掉,不用计算了,然后我就去掉这一部分,代码如下所示:
    /*1.实现一,质数肯定不能整除比自己一半还要大的数字*/
    int countPrimes(int n) 
    {
        if(n<=2) {
     return 0;
     }
    
    
     int i,j = 0;
     int count = 0;
     int flag = 0;
     
     for(i =3;i<=n;i++) {
     for(j = 2;j <= i/2;j++) {
     if(i%j == 0) {
     flag = 1;
     break;
     }
     }
     
     if(flag == 0) {
     count ++;
     } else {
     flag = 0;
     }
     }
     return (count+1);/*2也是质数*/
    }
    
    代码提交后,提示超时,通过第三次尝试解决联想一下,上面的情况是把n除以2之后,把后面一大部分去去掉(比如40/2 = 20 ,然后去掉20~40之间的数,不检查),那么除以3,除以4呢,是否可以以此类推? 通过运算验证,这个算法思路是正确的 那么从2开始,如果不能被整除,然后去掉后面1-1/2部分(对于40来说,就是20~40部分,大约20个数) 再从3开始,然后去掉后面1-1/3部分(对40来说,就是13~40部分,结合上面一步,本次省略了 13~20,也就是大约7个数) 在从4开始…… 如此一来,这样就可以减少了大量的不必要的测试项了! 做法:引入变量t = 1,每检查一次,t = t+1,然后 i = j / i
    /*2.实现二,
    如果n不能整除2,那么20-40这部分也是不能整除的
    如果n不能整除3,那么13~40这部分也是不能整除的
    以此类推
    */
    int countPrimes(int n) 
    {
        if(n<=2) {
    		return 0;
    	}
    
    
    	int i,j = 0;
    	int count = 0;
    	int flag = 0;
    	int t = 1;
    	
    	for(i =3;i<=n;i++) {
    		t = 1;
    		for(j = 2;j <= i/t;j++) {
    			if(i%j == 0) {
    				flag = 1;
    				break;
    			}
    			t++;
    		}
    		
    		if(flag == 0) {
    			count ++;
    		} else {
    			flag = 0;
    		}
    	}
    	return (count+1); /*2也是质数*/
    }
    
    【分析】结果还是超时了,文中提示采用埃拉托色尼筛选法,这个算法的过程如下图所示,我们从2开始遍历到根号n,先找到第一个质数2,然后将其所有的倍数全部标记出来,然后到下一个质数3,标记其所有倍数,一次类推,直到根号n,此时数组中未被标记的数字就是质数。我们需要一个n-1长度的bool型数组来记录每个数字是否被标记,长度为n-1的原因是题目说是小于n的质数个数,并不包括n。 然后我们用两个for循环来实现埃拉托斯特尼筛法,代码如下所示:
    /*实现三:
    筛选法
    */
    int countPrimes(int n) 
    {
    	if(n<=2) {
    		return 0;
    	}
    	/*用来标记*/
    	bool *isPrime = (bool *)malloc(sizeof(bool)*n);
    	memset(isPrime, 1, sizeof(bool)*n);
    	
       // Loop's ending condition is i * i < n instead of i < sqrt(n)
       // to avoid repeatedly calling an expensive function sqrt().
       for (int i = 2; i * i < n; i++) {
    	  if (!isPrime[i]) 
    		continue;
    	  for (int j = i * i; j < n; j += i) {
    		 isPrime[j] = 0;
    	  }
       }
       int count = 0;
       for (int i = 2; i < n; i++) {
    	  if (isPrime[i]) {
    		count++;
    	  }
       }
       return count;
    }
     
    方法四:用一个数组arr来存放这些质数,先给定arr[0] 为2,初始化,再用2来判断下一个值是否是质数,如果是,那么arr[1] == 该质数,再检查下一个数,检查是否可以被arr[0] 和 arr[1] 整除,以此类推
    
    分析:下面代码定义的数组长度为200,不用太大,因为第200个质数的值是1223,那么它的平方是:1495729,对于n = 1500000来说,绰绰有余了
    /*实现四:
    */
    int countPrimes(int n) 
    {
        if(n<=2) {
    		return 0;
    	}
    	
    	int i,j,k= 0;
    	int count = 0;
    	int flag = 0;
    	int t = 1;
    	int a[200];
    	a[0] = 2;
    
    
    	for(i =3;i<n;i++) {
    		
    		for(j =0;j <=k;j++) {
    			if(i%a[j] == 0) {
    				flag = 1;
    				break;
    			}
    		}
    		
    		if(flag == 0) {
    			if(k<199) {
    				k++;
    				a[k] = i;
    			}
    			count ++;
    			
    		} else {
    			flag = 0;
    		}
    	}
    	return (count+1);
    }
    
     
    相比较而言,方法三效率最高

你可能感兴趣的:(LeetCode)