I came across a LeetCode problem: Count the Number of Primes. Initially, I knew the brute-force solution, which involves checking all numbers up to n. I was also aware of the basic optimization where it’s sufficient to check up to √n for which the time complexity is O(√n). However, after reading the editorial, I discovered that the solution can be further optimized using the Sieve of Eratosthenes, achieving a time complexity of O(n log log n). Here’s the analysis:
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]: for j in range(i*i, n, i): is_prime[j] = False return sum(is_prime)
Analysis of Sieve of Eratosthenes
For math lovers,
Estimate the total number of assignments is_prime[j]=False over all iterations of:
for i in range(2, √n): if is_prime[i]: for j in range(i*i, n, i): ...
1: Inner Loop Analysis (Per i)
When i is prime, we mark off multiples of i starting from i2 up to <n.
So for a fixed i, the number of iterations is:
⌊in−i2⌋+1=⌊in−i⌋+1
Let’s denote this as:
T(i)=⌊in−i⌋+1
2: Number of Operations
We only run the inner loop for prime values of i from 2 to n.
Let π(x) be the number of primes less than or equal to x. Then:
i=2i prime∑nT(i)≈i=2∑n(in−i)
We simplify:
i=2∑n(in−i)=ni=2∑ni1−i=2∑ni
3: Estimate Harmonic Sum
The harmonic sum up to n is approximately:
i=2∑ni1≈ln(n)=21ln(n)
And the sum of integers from 2 to n is:
i=2∑ni=2n(n+1)−1≈2n
4: Combine Terms
Putting it together:
Total ops≈n⋅21lnn−2n=O(nlogn)
But this is an overestimate, because we’re summing over all i, not just the primes.
So we correct the estimate:
i=2i prime∑nin≈n⋅p≤n∑p1
And the sum of reciprocals of primes up to x is known to be:
p≤x∑p1≈lnlnx
Let $x = \sqrt{n}$, then:
lnlnn=ln(21lnn)=lnlnn+ln21
Which simplifies to:
p≤n∑pn≈nlnlnn
Conclusion
Time complexity of the inner loop across all iterations:
O(nloglogn)
Inner Loop Analysis
Counting Steps in the Sieve of Eratosthenes Inner Loop
We want to show:
⌊in−i2⌋+1=⌊in−i⌋+1
This expression counts the number of steps in the loop:
for j in range(i * i, n, i):
Algebraic Derivation
Start with the left-hand side:
⌊in−i2⌋+1
Break it apart:
=⌊in−ii2⌋+1=⌊in−i⌋+1
So both expressions are equal:
⌊in−i2⌋+1=⌊in−i⌋+1
Example
Let:
n=20,i=3,i2=9
The loop:
for j in range(9, 20, 3)
Produces:
[9,12,15,18](4 elements)
Left-hand side:
⌊320−9⌋+1=⌊311⌋+1=⌊3.666⌋+1=3+1=4
Right-hand side:
⌊320−3⌋+1=⌊6.666−3⌋+1=⌊3.666⌋+1=3+1=4
Conclusion
The two expressions are mathematically equivalent:
⌊in−i2⌋+1=⌊in−i⌋+1
They both count how many times the loop executes for a given i in the Sieve of Eratosthenes.