A clear explanation of counting prime numbers less than n using the Sieve of Eratosthenes.
Problem Restatement
We are given an integer n.
We need to count how many prime numbers are strictly less than n.
A prime number is a number greater than 1 that has exactly two positive divisors:
1- itself
The official statement says: given an integer n, return the number of prime numbers that are strictly less than n.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer n |
| Output | Number of primes less than n |
| Prime definition | Number greater than 1 with exactly two divisors |
| Important detail | Count numbers strictly less than n |
Example function shape:
def countPrimes(n: int) -> int:
...Examples
Example 1:
n = 10Prime numbers less than 10 are:
2, 3, 5, 7Count:
4Example 2:
n = 0There are no positive primes less than 0.
Answer:
0Example 3:
n = 2We count primes strictly less than 2.
There are none.
Answer:
0First Thought: Check Every Number
The direct idea is:
- Try every number from
2ton - 1. - Check whether each number is prime.
- Count how many are prime.
To check whether a number x is prime:
- Try dividing by every integer from
2tosqrt(x). - If any divisor works,
xis not prime.
Example:
x = 11Check:
11 % 2 != 0
11 % 3 != 0Since no divisor works, 11 is prime.
Problem With the Direct Method
Checking every number independently repeats a lot of work.
For example:
- While checking
12, we mark it non-prime. - Later while checking
18,24,30, and many others, we repeat similar divisibility work.
This becomes slow for large n.
We need a method that removes many composite numbers efficiently.
Key Insight: Sieve of Eratosthenes
Instead of testing each number independently, we can eliminate composite numbers in batches.
Idea:
- Start by assuming every number is prime.
- Pick a prime number.
- Mark all of its multiples as non-prime.
- Repeat.
For example:
2, 3, 4, 5, 6, 7, 8, 9, 10Start with 2.
All multiples of 2 greater than 2 are not prime:
4, 6, 8, 10Next prime is 3.
Mark multiples of 3:
6, 9Continue.
The remaining unmarked numbers are prime.
This algorithm is called the Sieve of Eratosthenes.
Why We Start From i * i
Suppose we are processing prime number i.
Example:
i = 5Multiples:
5 * 2 = 10
5 * 3 = 15
5 * 4 = 20
5 * 5 = 25The smaller multiples were already removed earlier:
10by215by320by2
So we only need to start from:
i * iThis avoids redundant work.
Algorithm
- If
n <= 2, return0. - Create a boolean array
is_prime. - Initially assume every number is prime.
- Mark
0and1as non-prime. - For each number
ifrom2tosqrt(n):- If
iis prime:- Mark all multiples starting from
i * ias non-prime.
- Mark all multiples starting from
- If
- Count how many values remain
True.
Walkthrough
Use:
n = 10Initial table:
| Number | Prime? |
|---|---|
| 0 | False |
| 1 | False |
| 2 | True |
| 3 | True |
| 4 | True |
| 5 | True |
| 6 | True |
| 7 | True |
| 8 | True |
| 9 | True |
Process 2:
Mark:
4, 6, 8as non-prime.
Process 3:
Mark:
9as non-prime.
Final primes:
2, 3, 5, 7Count:
4Correctness
Initially, the algorithm assumes every integer greater than 1 is prime.
Whenever the algorithm processes a prime number p, it marks every multiple of p greater than or equal to p^2 as non-prime.
Every marked number is composite because it has at least two factors:
p × kfor some integer k >= p.
Now consider any composite number x.
Since x is composite, it has a prime factor p such that:
p <= sqrt(x)When the algorithm processes p, it marks all multiples of p, including x, as non-prime.
Therefore every composite number is eventually removed.
Any number that remains unmarked cannot have any divisor greater than 1 and smaller than itself, so it must be prime.
Thus the algorithm counts exactly the prime numbers less than n.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n log log n) | Sieve marking process |
| Space | O(n) | Boolean array |
The sieve is much faster than checking every number independently.
Implementation
class Solution:
def countPrimes(self, n: int) -> int:
if n <= 2:
return 0
is_prime = [True] * n
is_prime[0] = False
is_prime[1] = False
i = 2
while i * i < n:
if is_prime[i]:
j = i * i
while j < n:
is_prime[j] = False
j += i
i += 1
return sum(is_prime)Code Explanation
Create the prime table:
is_prime = [True] * nInitially every number is assumed prime.
Mark 0 and 1:
is_prime[0] = False
is_prime[1] = FalseThe outer loop processes candidate primes:
while i * i < n:We only need to process up to sqrt(n).
If i is still prime:
if is_prime[i]:start marking multiples:
j = i * iContinue marking:
is_prime[j] = False
j += iFinally count all remaining prime entries:
return sum(is_prime)In Python, True behaves like 1, so summing the boolean list counts primes.
Optimized Python Version
Python slicing can mark multiples more compactly:
class Solution:
def countPrimes(self, n: int) -> int:
if n <= 2:
return 0
is_prime = [True] * n
is_prime[0] = is_prime[1] = False
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
is_prime[i * i:n:i] = [False] * len(range(i * i, n, i))
return sum(is_prime)The earlier version is usually easier to understand during interviews.
Testing
def run_tests():
s = Solution()
assert s.countPrimes(10) == 4
assert s.countPrimes(0) == 0
assert s.countPrimes(1) == 0
assert s.countPrimes(2) == 0
assert s.countPrimes(3) == 1
assert s.countPrimes(100) == 25
print("all tests passed")
run_tests()| Test | Why |
|---|---|
10 | Standard example |
0 | Small boundary |
1 | No primes |
2 | Strictly less than 2 |
3 | Smallest nonzero answer |
100 | Larger correctness check |