You receive one integer n. Count how many prime numbers sit below n, and hand that count back.
Recall that a prime is any whole number bigger than 1 whose only positive divisors are 1 and the number itself.
Sieve of Eratosthenes
upper bound n ➔ boolean sieve array ➔ prime count| 1 | def count_primes(n: int) -> int: |
| 2 | """Sieve of Eratosthenes: counts primes strictly below n. |
| 3 | Time: O(N log log N) | Space: O(N) |
| 4 | """ |
| 5 | if n <= 2: |
| 6 | return 0 # No primes below 2 |
| 7 | |
| 8 | is_prime = [True] * n |
| 9 | is_prime[0] = is_prime[1] = False # 0 and 1 are not prime |
| 10 | |
| 11 | for p in range(2, int(n ** 0.5) + 1): # Factors above sqrt(n) were already crossed out |
| 12 | if is_prime[p]: |
| 13 | for multiple in range(p * p, n, p): # Start at p*p: smaller multiples have a smaller factor |
| 14 | is_prime[multiple] = False |
| 15 | |
| 16 | return sum(is_prime) |
p, is_prime[q] is already correct for every q < p * p, so is_prime[p] is True exactly when p is prime.A number is composite exactly when it has a prime factor no larger than its square root, so cross off multiples only for primes p up to sqrt(n). Start each crossing at p * p: every smaller multiple p * k with k < p was already crossed off by a smaller prime factor of k. Whatever is still True in is_prime is prime. The trap is 0 and 1: they start True like every slot, but no crossing ever reaches them, so set both to False first or the count comes out 2 too high.
See it run
Sieve of Eratosthenes, step by step
p -primes left 30
Each cell is one slot of is_prime, read left to right in rows of 6. The number is the row label plus the column offset. An x means the slot is False.
pthe prime whose multiples are crossed nowcrosseda number marked False in this stepskipa value of p that is already crossed, so its loop is skippedprimea number still True at the end
is_prime = [True] * 30. Every slot from 0 to 29 starts as True, so every number is still a candidate.
Gotcha 1 of 5
- The trap
Gotcha 1: Leaving
is_prime[0]andis_prime[1]as TrueMistakeis_prime = [True] * nFixis_prime = [True] * n is_prime[0] = is_prime[1] = FalseWhat happens: Every slot starts True. Crossing out starts at
p * p, which is at least 4. So no step ever reaches 0 or 1. Without the line,sum(is_prime)counts 0 and 1 as primes, and the count is 2 too high.Example: n = 10Mistake: returns 6Fix: returns 4
- Only a few fixed primes appear and the length can reach 10^15, as in Count Good Numbers (LC 1922): the hard part is one huge power, not finding primes. See Fast Power
Deep Dive
Why crossing out multiples finds every prime
A prime is a number above 1 that has no divisor except 1 and itself. A composite number is a number above 1 that is not prime. Every composite number has a prime factor. So if you cross out all multiples of every prime, only primes remain.
is_prime[q] means "no smaller prime has divided q so far". When the loop reaches p and is_prime[p] is still True, no prime below p divides p. So p is prime. This is the invariant: is_prime[q] is already correct for every q < p * p.
Why q < p * p? A composite q has a prime factor of at most sqrt(q). If q < p * p, that factor is smaller than p. The loop already crossed q out when it handled that factor.
Why the crossing starts at p * p and p stops at the square root
A multiple p * k with k < p has the factor k. The smallest prime factor of k is smaller than p, so a smaller prime already crossed p * k out. The first multiple of p that nothing smaller has touched is p * p.
The same fact ends the outer loop. A composite number below n has a prime factor of at most sqrt(n). The outer loop range(2, int(n ** 0.5) + 1) visits all of those primes. A prime p above sqrt(n) has p * p >= n, so its inner loop would be empty.
The + 1 is needed because range stops before its end value. For n = 10, int(10 ** 0.5) is 3. Without + 1, p = 3 never runs, and 9 stays in the table as a prime. The answer is 5 instead of 4.
Trace for n = 30: 10 primes below 30
The table has slots 0 to 29. First is_prime[0] = is_prime[1] = False. The loop p runs from 2 to int(30 ** 0.5), which is 5.
p = 2 is prime. It crosses 4, 6, 8, ... 28, which is 13 numbers. p = 3 is prime. It starts at 9 and crosses 9, 15, 21 and 27. The numbers 12, 18 and 24 were already crossed by 2. p = 4 is crossed out, so its loop is skipped. p = 5 is prime and crosses only 25, because 30 is outside the table.
The numbers still True are 2, 3, 5, 7, 11, 13, 17, 19, 23 and 29. That is 10 primes, and sum(is_prime) returns 10. For the Count Primes (LC 204) example n = 10, the same steps leave 2, 3, 5 and 7, so the answer is 4.
Without the line for 0 and 1, the sum would count them too and return 12 for n = 30, or 6 for n = 10.
Cost: O(n log log n) time and O(n) space
The space is the table: n booleans. The time is the number of crossings plus the final sum.
Prime p crosses about n / p numbers. The total over all primes up to n is n times the sum of 1 / p, and that sum grows like log log n. This is why the cost is O(n log log n), only a little more than O(n).
Measured with the card's own loop: for n = 100, the crossings start at p * p and make 102 writes. Starting at 2 * p would make 111 writes for the same answer. For n = 10**6, the sieve makes about 2.1 million writes for a table of one million slots.
The table itself limits the sieve. It needs n slots, so it suits limits up to roughly tens of millions. For one number x, trial division up to sqrt(x) is simpler and uses no table.
60-Second Pitch
"I need the primes below n, so I build a boolean table is_prime of n slots and set slots 0 and 1 to False. For each p from 2 to the integer square root of n, if is_prime[p] is still True, I cross out its multiples starting at p * p. A smaller multiple has a smaller prime factor, so it was already crossed. The outer loop stops at the square root because every composite number below n has a prime factor of at most that size. The answer is sum(is_prime). The time is O(n log log n) and the space is O(n). I check three things: slots 0 and 1, the table size n and not n + 1, and the + 1 in the range. For n = 10, the answer is 4."
Common Questions
- Why does the inner loop start at
p * pand not at2 * p? A multiple
p * kwithk < phas a prime factor smaller thanp, and that smaller prime already crossed it out. Starting atp * pskips those repeated writes. Forn = 100it saves 9 writes. The answer is the same either way.- Why can the outer loop stop at the square root of
n? A composite number below
nhas a prime factor of at mostsqrt(n). So crossing out the multiples of primes up tosqrt(n)removes every composite number. A largerphasp * p >= n, so its inner loop does nothing.- What is the time and space complexity, and why is the time not O(n^2)?
Time is O(n log log n) and space is
O(n). Primepwrites aboutn / pslots, and the sum ofn / pover primes up tonis aboutn log log n. Only primes cross out multiples, so composite values ofpadd no work.- When would you not use a sieve?
Use trial division when you test one or two numbers, because the sieve builds a table of
nslots first. Also avoid it when the limit is too large for memory. The sieve pays off when many values below one limit need a prime test.- Why does Count Primes (LC 204) use
[True] * nand notn + 1? The problem counts primes strictly less than
n, so the table needs slots 0 ton - 1. Withn + 1slots, slotnis never crossed out, because the inner loop stops beforen. A non-primenis then counted as a prime.
SWE Reasoning
Spot it
- The problem asks how many numbers below a known limit are prime (Count Primes (LC 204)).
- The problem needs a prime test for many values below one limit, so one table can answer all of them (Prime Arrangements (LC 1175), Prime Subtraction Operation (LC 2601), Prime Pairs With Target Sum (LC 2761)).
- The limit
nis small enough to hold a boolean table in memory. - The test is a property of each number (prime or not), not the value of one huge power. For a huge power, use Fast Power.
The invariant, step by step
is_primehasnslots, allTrue. Slots 0 and 1 are set toFalseat once.- The loop takes
pfrom 2 upward. When it reachesp, every slotq < p * pis correct. - If
is_prime[p]isTrue, no smaller prime dividesp, sopis prime. - The inner loop sets
is_prime[multiple]toFalseforp * p,p * p + p, and so on belown. - After that, every slot
q < (p + 1) * (p + 1)is correct, because a compositeqin that range has a prime factor of at mostp. - When
ppassesint(n ** 0.5), every slot belownis correct, andsum(is_prime)counts the primes.
Traps
- Slots 0 and 1 left as
True. No crossing starts below 4.sum(is_prime)counts both and is 2 too high.count_primes(10)returns 6 instead of 4. - Table of
n + 1slots. Count Primes (LC 204) counts primes strictly belown. The extra slot fornis never crossed, so a non-primenis counted.count_primes(10)returns 5. range(2, int(n ** 0.5))without+ 1.pnever reaches the integer square root. Forn = 10, 9 is not crossed and the answer is 5.- Crossing from
2 * p. The answer is correct, but the code writes to the same slots again. Forn = 100, it makes 111 writes instead of 102. - A sieve for one number. Building a table of
nslots to test onexcosts more than trial division up tosqrt(x).
Practice
You receive one integer n. Count how many prime numbers sit below n, and hand that count back.
Recall that a prime is any whole number bigger than 1 whose only positive divisors are 1 and the number itself.
Sieve of Eratosthenes
upper bound n ➔ boolean sieve array ➔ prime count| 1 | def count_primes(n: int) -> int: |
| 2 | """Sieve of Eratosthenes: counts primes strictly below n. |
| 3 | Time: O(N log log N) | Space: O(N) |
| 4 | """ |
| 5 | if n <= 2: |
| 6 | return 0 # No primes below 2 |
| 7 | |
| 8 | is_prime = [True] * n |
| 9 | is_prime[0] = is_prime[1] = False # 0 and 1 are not prime |
| 10 | |
| 11 | for p in range(2, int(n ** 0.5) + 1): # Factors above sqrt(n) were already crossed out |
| 12 | if is_prime[p]: |
| 13 | for multiple in range(p * p, n, p): # Start at p*p: smaller multiples have a smaller factor |
| 14 | is_prime[multiple] = False |
| 15 | |
| 16 | return sum(is_prime) |
p, is_prime[q] is already correct for every q < p * p, so is_prime[p] is True exactly when p is prime.A number is composite exactly when it has a prime factor no larger than its square root, so cross off multiples only for primes p up to sqrt(n). Start each crossing at p * p: every smaller multiple p * k with k < p was already crossed off by a smaller prime factor of k. Whatever is still True in is_prime is prime. The trap is 0 and 1: they start True like every slot, but no crossing ever reaches them, so set both to False first or the count comes out 2 too high.
See it run
Sieve of Eratosthenes, step by step
p -primes left 30
Each cell is one slot of is_prime, read left to right in rows of 6. The number is the row label plus the column offset. An x means the slot is False.
pthe prime whose multiples are crossed nowcrosseda number marked False in this stepskipa value of p that is already crossed, so its loop is skippedprimea number still True at the end
is_prime = [True] * 30. Every slot from 0 to 29 starts as True, so every number is still a candidate.
Gotcha 1 of 5
- The trap
Gotcha 1: Leaving
is_prime[0]andis_prime[1]as TrueMistakeis_prime = [True] * nFixis_prime = [True] * n is_prime[0] = is_prime[1] = FalseWhat happens: Every slot starts True. Crossing out starts at
p * p, which is at least 4. So no step ever reaches 0 or 1. Without the line,sum(is_prime)counts 0 and 1 as primes, and the count is 2 too high.Example: n = 10Mistake: returns 6Fix: returns 4
- Only a few fixed primes appear and the length can reach 10^15, as in Count Good Numbers (LC 1922): the hard part is one huge power, not finding primes. See Fast Power
Deep Dive
Why crossing out multiples finds every prime
A prime is a number above 1 that has no divisor except 1 and itself. A composite number is a number above 1 that is not prime. Every composite number has a prime factor. So if you cross out all multiples of every prime, only primes remain.
is_prime[q] means "no smaller prime has divided q so far". When the loop reaches p and is_prime[p] is still True, no prime below p divides p. So p is prime. This is the invariant: is_prime[q] is already correct for every q < p * p.
Why q < p * p? A composite q has a prime factor of at most sqrt(q). If q < p * p, that factor is smaller than p. The loop already crossed q out when it handled that factor.
Why the crossing starts at p * p and p stops at the square root
A multiple p * k with k < p has the factor k. The smallest prime factor of k is smaller than p, so a smaller prime already crossed p * k out. The first multiple of p that nothing smaller has touched is p * p.
The same fact ends the outer loop. A composite number below n has a prime factor of at most sqrt(n). The outer loop range(2, int(n ** 0.5) + 1) visits all of those primes. A prime p above sqrt(n) has p * p >= n, so its inner loop would be empty.
The + 1 is needed because range stops before its end value. For n = 10, int(10 ** 0.5) is 3. Without + 1, p = 3 never runs, and 9 stays in the table as a prime. The answer is 5 instead of 4.
Trace for n = 30: 10 primes below 30
The table has slots 0 to 29. First is_prime[0] = is_prime[1] = False. The loop p runs from 2 to int(30 ** 0.5), which is 5.
p = 2 is prime. It crosses 4, 6, 8, ... 28, which is 13 numbers. p = 3 is prime. It starts at 9 and crosses 9, 15, 21 and 27. The numbers 12, 18 and 24 were already crossed by 2. p = 4 is crossed out, so its loop is skipped. p = 5 is prime and crosses only 25, because 30 is outside the table.
The numbers still True are 2, 3, 5, 7, 11, 13, 17, 19, 23 and 29. That is 10 primes, and sum(is_prime) returns 10. For the Count Primes (LC 204) example n = 10, the same steps leave 2, 3, 5 and 7, so the answer is 4.
Without the line for 0 and 1, the sum would count them too and return 12 for n = 30, or 6 for n = 10.
Cost: O(n log log n) time and O(n) space
The space is the table: n booleans. The time is the number of crossings plus the final sum.
Prime p crosses about n / p numbers. The total over all primes up to n is n times the sum of 1 / p, and that sum grows like log log n. This is why the cost is O(n log log n), only a little more than O(n).
Measured with the card's own loop: for n = 100, the crossings start at p * p and make 102 writes. Starting at 2 * p would make 111 writes for the same answer. For n = 10**6, the sieve makes about 2.1 million writes for a table of one million slots.
The table itself limits the sieve. It needs n slots, so it suits limits up to roughly tens of millions. For one number x, trial division up to sqrt(x) is simpler and uses no table.
60-Second Pitch
"I need the primes below n, so I build a boolean table is_prime of n slots and set slots 0 and 1 to False. For each p from 2 to the integer square root of n, if is_prime[p] is still True, I cross out its multiples starting at p * p. A smaller multiple has a smaller prime factor, so it was already crossed. The outer loop stops at the square root because every composite number below n has a prime factor of at most that size. The answer is sum(is_prime). The time is O(n log log n) and the space is O(n). I check three things: slots 0 and 1, the table size n and not n + 1, and the + 1 in the range. For n = 10, the answer is 4."
Common Questions
- Why does the inner loop start at
p * pand not at2 * p? A multiple
p * kwithk < phas a prime factor smaller thanp, and that smaller prime already crossed it out. Starting atp * pskips those repeated writes. Forn = 100it saves 9 writes. The answer is the same either way.- Why can the outer loop stop at the square root of
n? A composite number below
nhas a prime factor of at mostsqrt(n). So crossing out the multiples of primes up tosqrt(n)removes every composite number. A largerphasp * p >= n, so its inner loop does nothing.- What is the time and space complexity, and why is the time not O(n^2)?
Time is O(n log log n) and space is
O(n). Primepwrites aboutn / pslots, and the sum ofn / pover primes up tonis aboutn log log n. Only primes cross out multiples, so composite values ofpadd no work.- When would you not use a sieve?
Use trial division when you test one or two numbers, because the sieve builds a table of
nslots first. Also avoid it when the limit is too large for memory. The sieve pays off when many values below one limit need a prime test.- Why does Count Primes (LC 204) use
[True] * nand notn + 1? The problem counts primes strictly less than
n, so the table needs slots 0 ton - 1. Withn + 1slots, slotnis never crossed out, because the inner loop stops beforen. A non-primenis then counted as a prime.
SWE Reasoning
Spot it
- The problem asks how many numbers below a known limit are prime (Count Primes (LC 204)).
- The problem needs a prime test for many values below one limit, so one table can answer all of them (Prime Arrangements (LC 1175), Prime Subtraction Operation (LC 2601), Prime Pairs With Target Sum (LC 2761)).
- The limit
nis small enough to hold a boolean table in memory. - The test is a property of each number (prime or not), not the value of one huge power. For a huge power, use Fast Power.
The invariant, step by step
is_primehasnslots, allTrue. Slots 0 and 1 are set toFalseat once.- The loop takes
pfrom 2 upward. When it reachesp, every slotq < p * pis correct. - If
is_prime[p]isTrue, no smaller prime dividesp, sopis prime. - The inner loop sets
is_prime[multiple]toFalseforp * p,p * p + p, and so on belown. - After that, every slot
q < (p + 1) * (p + 1)is correct, because a compositeqin that range has a prime factor of at mostp. - When
ppassesint(n ** 0.5), every slot belownis correct, andsum(is_prime)counts the primes.
Traps
- Slots 0 and 1 left as
True. No crossing starts below 4.sum(is_prime)counts both and is 2 too high.count_primes(10)returns 6 instead of 4. - Table of
n + 1slots. Count Primes (LC 204) counts primes strictly belown. The extra slot fornis never crossed, so a non-primenis counted.count_primes(10)returns 5. range(2, int(n ** 0.5))without+ 1.pnever reaches the integer square root. Forn = 10, 9 is not crossed and the answer is 5.- Crossing from
2 * p. The answer is correct, but the code writes to the same slots again. Forn = 100, it makes 111 writes instead of 102. - A sieve for one number. Building a table of
nslots to test onexcosts more than trial division up tosqrt(x).