Before attempting this problem, you should be comfortable with:
Prime Number Definition - Understanding that a prime number has exactly two divisors: 1 and itself
Square Root Optimization - Knowing that factors come in pairs, so checking up to sqrt(n) is sufficient
Sieve of Eratosthenes - Understanding this classic algorithm for efficiently finding all primes up to a limit
1. Brute Force
Intuition
The most straightforward way to count primes is to check each number individually. A number is prime if it has no divisors other than 1 and itself. We can optimize the divisibility check by only testing divisors up to the square root of the number, since if a number has a factor larger than its square root, it must also have a corresponding factor smaller than its square root.
Algorithm
Initialize a counter res to 0.
For each number from 2 to n-1:
Assume the number is prime.
Check if any number from 2 to the square root of the current number divides it evenly.
classSolution:defcountPrimes(self, n:int)->int:
res =0for num inrange(2, n):
isPrime =Truefor i inrange(2,int(num **0.5)+1):if num % i ==0:
isPrime =Falsebreakif isPrime:
res +=1return res
publicclassSolution{publicintcountPrimes(int n){int res =0;for(int num =2; num < n; num++){boolean isPrime =true;for(int i =2; i * i <= num; i++){if(num % i ==0){
isPrime =false;break;}}if(isPrime){
res++;}}return res;}}
classSolution{public:intcountPrimes(int n){int res =0;for(int num =2; num < n; num++){bool isPrime =true;for(int i =2; i * i <= num; i++){if(num % i ==0){
isPrime =false;break;}}if(isPrime) res++;}return res;}};
classSolution{/**
* @param {number} n
* @return {number}
*/countPrimes(n){let res =0;for(let num =2; num < n; num++){let isPrime =true;for(let i =2; i * i <= num; i++){if(num % i ===0){
isPrime =false;break;}}if(isPrime) res++;}return res;}}
publicclassSolution{publicintCountPrimes(int n){int res =0;for(int num =2; num < n; num++){bool isPrime =true;for(int i =2; i * i <= num; i++){if(num % i ==0){
isPrime =false;break;}}if(isPrime){
res++;}}return res;}}
funccountPrimes(n int)int{
res :=0for num :=2; num < n; num++{
isPrime :=truefor i :=2; i*i <= num; i++{if num%i ==0{
isPrime =falsebreak}}if isPrime {
res++}}return res
}
class Solution {funcountPrimes(n: Int): Int {var res =0for(num in2 until n){var isPrime =truevar i =2while(i * i <= num){if(num % i ==0){
isPrime =falsebreak}
i++}if(isPrime){
res++}}return res
}}
classSolution{funccountPrimes(_ n:Int)->Int{var res =0for num in2..<n {var isPrime =truevar i =2while i * i <= num {if num % i ==0{
isPrime =falsebreak}
i +=1}if isPrime {
res +=1}}return res
}}
Time & Space Complexity
Time complexity: O(nn)
Space complexity: O(1)
2. Sieve of Eratosthenes
Intuition
Instead of checking each number for primality, we can use the Sieve of Eratosthenes to efficiently mark composite numbers. The key insight is that when we find a prime number, all of its multiples must be composite. By marking these multiples, we eliminate the need to check them later. We start marking from the square of each prime because smaller multiples would have already been marked by smaller primes.
Algorithm
Create a boolean array sieve of size n, initialized to false (all numbers assumed prime).
Initialize a counter res to 0.
For each number from 2 to n-1:
If the number is not marked as composite (sieve[num] is false):
Increment the prime counter.
Mark all multiples of this number starting from num*num as composite.
classSolution:defcountPrimes(self, n:int)->int:
sieve =[False]* n
res =0for num inrange(2, n):ifnot sieve[num]:
res +=1for i inrange(num * num, n, num):
sieve[i]=Truereturn res
publicclassSolution{publicintcountPrimes(int n){boolean[] sieve =newboolean[n];int res =0;for(int num =2; num < n; num++){if(!sieve[num]){
res++;for(long i = num *1L* num; i < n; i += num){
sieve[(int) i]=true;}}}return res;}}
classSolution{public:intcountPrimes(int n){
vector<bool>sieve(n,false);int res =0;for(int num =2; num < n; num++){if(!sieve[num]){
res++;for(longlong i =1LL* num * num; i < n; i += num){
sieve[i]=true;}}}return res;}};
classSolution{/**
* @param {number} n
* @return {number}
*/countPrimes(n){const sieve =newArray(n).fill(false);let res =0;for(let num =2; num < n; num++){if(!sieve[num]){
res++;for(let i = num * num; i < n; i += num){
sieve[i]=true;}}}return res;}}
publicclassSolution{publicintCountPrimes(int n){bool[] sieve =newbool[n];int res =0;for(int num =2; num < n; num++){if(!sieve[num]){
res++;for(long i =(long)num * num; i < n; i += num){
sieve[(int)i]=true;}}}return res;}}
Time & Space Complexity
Time complexity: O(nlog(logn))
Space complexity: O(n)
3. Sieve of Eratosthenes (Optimal)
Intuition
We can optimize the sieve by observing that 2 is the only even prime. Instead of processing all numbers, we start by assuming half the numbers (all odd numbers plus 2) are prime, then only sieve odd numbers starting from 3. When marking multiples, we skip even multiples since they are already excluded. This cuts the work roughly in half.
Algorithm
If n < 3, return 0 (no primes less than 2).
Initialize count to n/2 (assuming 2 and all odd numbers are prime).
Create a boolean array isPrime of size n.
For each odd number i from 3 up to sqrt(n):
If i is not marked as composite:
Mark all odd multiples of i starting from i*i as composite.