Problem:
Description:
Count the number of prime numbers less than a non-negative number, n.
Analysis:
The idea to test if a number is prime. Reference: https://en.wikipedia.org/wiki/Prime_number Prime number is an important category of problems. It is not hard, but require some skills in implementing. A trivial idea for solving this problem is, test every number from "2" to "n-1" individually. The imprtant part of this idea is how to test if a number is prime number. One simple pattern is: Checking if a num could be divided by an integer(larger than 1), if it is, it is not a prime number. Definition: A prime number (or a prime) is a natural number greater than 1 that has no positive divisors other than 1 and itself. A solution: private boolean testPrime(int num) { if (num < 2) return false; for (int i = 2; i <= num / 2; i++) if (num % i == 0) return false; } return true; } We need to stop i at num/2, since after that : num / i < 2 (a fractation (2, 1]). Actually, we stop i more early, since "after num / sqrt(num) <= sqrt(num)" which means we repeat the checking if we cointue checking. private boolean testPrime(int num) { if (num < 2) return false; for (int i = 2; i <= Math.sqrt(num); i++) { if (num % i == 0) return false; } return true; } Cases: num = 12. 1. use "for (int i = 2; i <= num / 2; i++)" test: 2, 3, 4, 5, 6 2. use "for (int i = 2; i <= Math.sqrt(num); i++)"" test: 2, 3 test (num % 2) is no differnet than test (num % 6) test (num % 3) is no different thant test (num % 4) To many repeatives in the unimproved solution. Exceed time solution: public class Solution { public int countPrimes(int n) { if (n < 2) return 0; int count = 0; for (int i = 2; i < n; i++) { if (testPrime(i)) count++; } return count; } private boolean testPrime(int num) { if (num < 2) return false; //for (int i = 2; i <= num / 2; i++) for (int i = 2; i <= Math.sqrt(num); i++) { if (num % i == 0) return false; } return true; } } ------------------------------------------------------------------- The above solution is perfect in testing against a single integer, but for distinguishing a series of integers. The time cost is too high. Thus we have to use "Sieve of Eratosthenes" method. Reference: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes Basic idea: In the trivial solution, we repeatedly to check a integer against [2, ... celi(sqrt(i))]. All integer need to check against 2 , 3 ... again and again. Is that necessary, can we avoid it? Yes, by using a check_board, we check "forward" rather than "backward"! For a prime number p, we tag all its mulitple "p*2" "p*3" .... Thus each number actually only check against with one number. For the detail of the algorithm, please refer the reference link. Skill: 1. if we tag sequentially, the left-most untagged number must be prime number. Since all numbers smaller than it could not be a divisor of it (otherwise, the number was tagged). Not to say the number large than it. 2. to tag non-prime number, we only need to start from "i^2" then "i^2+i" ... The reason is simple. all (i-1) * i has already been tested. for (int j = i*i; j < n; j=j+i) { check_board[j] = false; }
Efficient Soltuion:
public class Solution { public int countPrimes(int n) { if (n < 2) return 0; boolean[] check_board = new boolean[n+1]; //note this pitfall!!! Arrays.fill(check_board, true); int count = 0; for (int i = 2; i < n; i++) { if (i < Math.sqrt(n)) { if (check_board[i] == false) { continue; } else{ count++; for (int j = i*i; j < n; j=j+i) { check_board[j] = false; } } } else { if (check_board[i] == true) count++; } } return count; } }
Clear Solution:
public class Solution { public int countPrimes(int n) { if (n < 2) return 0; boolean[] check_board = new boolean[n+1]; //note this pitfall!!! Arrays.fill(check_board, true); int count = 0; for (int i = 2; i < n; i++) { if (i < Math.sqrt(n) && check_board[i] == true) { for (int j = i*i; j < n; j=j+i) { check_board[j] = false; } } } for (int i = 2; i < n; i++) { if (check_board[i] == true) count++; } return count; } }