204. Count Primes
Problem Description
Given an integer n
, you need to count how many prime numbers exist that are strictly less than n
.
A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. For example, 2, 3, 5, 7, 11 are prime numbers, while 4, 6, 8, 9 are not prime.
The solution uses the Sieve of Eratosthenes algorithm, which is an efficient method for finding all primes up to a given number:
- Create a boolean array
primes
of sizen
, initially marking all numbers as potentially prime (True
) - Start iterating from 2 (the smallest prime number) up to
n-1
- For each number
i
:- If
primes[i]
is stillTrue
, it's a prime number, so increment the counter - Mark all multiples of
i
(starting from2i
,3i
,4i
, ...) as non-prime by setting them toFalse
- If
- Return the total count of prime numbers found
The key insight is that if a number i
is prime, then all its multiples (2i
, 3i
, 4i
, etc.) cannot be prime. By systematically eliminating these multiples, we efficiently identify all prime numbers less than n
.
For example, if n = 10
:
- Prime numbers less than 10 are: 2, 3, 5, 7
- The function would return 4
Intuition
The naive approach would be to check each number from 2 to n-1
individually to see if it's prime. For each number, we'd need to check if it has any divisors, which would take O(n * sqrt(n))
time overall. This becomes inefficient for large values of n
.
The key observation is that we can leverage the relationship between prime numbers and their multiples. Once we identify a number as prime, we immediately know that all its multiples cannot be prime. For instance, if we know 2 is prime, we can immediately mark 4, 6, 8, 10... as non-prime without checking them individually.
This leads us to think about a "marking" or "elimination" strategy: instead of checking if each number is prime, we can start with the assumption that all numbers might be prime, then systematically eliminate the non-primes.
The process works like finding prime numbers on a number line: when we encounter a prime number i
, we cross out all its multiples (2i
, 3i
, 4i
...). What remains uncrossed are the prime numbers. This ancient technique, known as the Sieve of Eratosthenes, dramatically reduces redundant calculations.
Why is this efficient? Because each composite number gets marked exactly by its prime factors, and we never need to perform division or modulo operations to check primality. We're essentially trading space (the boolean array) for time efficiency, transforming the problem from repeated primality testing to simple array marking operations.
Solution Approach
The implementation uses the Sieve of Eratosthenes algorithm with a boolean array to track prime numbers:
Step 1: Initialize the data structure
primes = [True] * n ans = 0
We create a boolean array primes
of size n
where primes[i]
represents whether number i
is potentially prime. Initially, all values are set to True
. We also initialize a counter ans
to track the number of primes found.
Step 2: Iterate through potential primes
for i in range(2, n):
We start from 2 (the smallest prime) and iterate up to n-1
. Numbers 0 and 1 are not prime by definition, so we skip them.
Step 3: Check and count primes
if primes[i]: ans += 1
If primes[i]
is still True
, then i
hasn't been marked as composite by any smaller prime, meaning i
itself is prime. We increment our counter.
Step 4: Mark multiples as non-prime
for j in range(i + i, n, i):
primes[j] = False
For each prime i
, we mark all its multiples as non-prime. Starting from 2i
(which is i + i
), we mark every i
-th number up to n
as False
. The loop uses a step size of i
to efficiently jump to each multiple.
Step 5: Return the result
return ans
After processing all numbers, ans
contains the total count of prime numbers less than n
.
The algorithm's efficiency comes from the fact that each composite number is marked only once for each of its prime factors, and we avoid redundant primality checks. The time complexity is O(n log log n)
and space complexity is O(n)
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with n = 12
to find all prime numbers less than 12.
Initial Setup:
- Create array
primes = [True, True, True, True, True, True, True, True, True, True, True, True]
(indices 0-11) - Initialize counter
ans = 0
Iteration 1: i = 2
primes[2] = True
, so 2 is prime →ans = 1
- Mark multiples of 2 as non-prime:
primes[4] = False
(2×2)primes[6] = False
(2×3)primes[8] = False
(2×4)primes[10] = False
(2×5)
- Array now:
[True, True, True, True, False, True, False, True, False, True, False, True]
Iteration 2: i = 3
primes[3] = True
, so 3 is prime →ans = 2
- Mark multiples of 3 as non-prime:
primes[6] = False
(already False)primes[9] = False
(3×3)
- Array now:
[True, True, True, True, False, True, False, True, False, False, False, True]
Iteration 3: i = 4
primes[4] = False
(marked by 2), skip marking multiples
Iteration 4: i = 5
primes[5] = True
, so 5 is prime →ans = 3
- Mark multiples of 5 as non-prime:
primes[10] = False
(already False)
- Array now:
[True, True, True, True, False, True, False, True, False, False, False, True]
Iteration 5: i = 6
primes[6] = False
(marked by 2), skip
Iteration 6: i = 7
primes[7] = True
, so 7 is prime →ans = 4
- Mark multiples of 7 as non-prime:
- None within range (7×2 = 14 > 11)
Iterations 7-10: i = 8, 9, 10, 11
primes[8] = False
(marked by 2), skipprimes[9] = False
(marked by 3), skipprimes[10] = False
(marked by 2), skipprimes[11] = True
, so 11 is prime →ans = 5
Result: The prime numbers less than 12 are: 2, 3, 5, 7, 11
Return: ans = 5
The key insight demonstrated here is how each composite number gets eliminated by its smallest prime factor, avoiding redundant checks. For instance, 6 is marked as non-prime when processing 2 (since 6 = 2×3), and we never need to check it again when processing 3.
Solution Implementation
1class Solution:
2 def countPrimes(self, n: int) -> int:
3 """
4 Count the number of prime numbers less than n using the Sieve of Eratosthenes algorithm.
5
6 Args:
7 n: Upper bound (exclusive) for counting primes
8
9 Returns:
10 Number of prime numbers less than n
11 """
12 # Edge case: no primes less than 2
13 if n <= 2:
14 return 0
15
16 # Initialize a boolean array where True means the index is potentially prime
17 # Index represents the number itself (0 to n-1)
18 is_prime = [True] * n
19
20 # 0 and 1 are not prime numbers
21 is_prime[0] = is_prime[1] = False
22
23 prime_count = 0
24
25 # Iterate through all numbers from 2 to n-1
26 for current_num in range(2, n):
27 # If current_num hasn't been marked as composite
28 if is_prime[current_num]:
29 # Count this prime number
30 prime_count += 1
31
32 # Mark all multiples of current_num as composite (not prime)
33 # Start from current_num * 2 and increment by current_num
34 for multiple in range(current_num * 2, n, current_num):
35 is_prime[multiple] = False
36
37 return prime_count
38
1class Solution {
2 /**
3 * Count the number of prime numbers less than n using the Sieve of Eratosthenes algorithm.
4 * @param n The upper bound (exclusive) for counting primes
5 * @return The count of prime numbers less than n
6 */
7 public int countPrimes(int n) {
8 // Edge case: no primes less than 2
9 if (n <= 2) {
10 return 0;
11 }
12
13 // Initialize boolean array to track prime status
14 // isPrime[i] represents whether number i is prime
15 boolean[] isPrime = new boolean[n];
16 Arrays.fill(isPrime, true);
17
18 // 0 and 1 are not prime numbers
19 isPrime[0] = false;
20 isPrime[1] = false;
21
22 int primeCount = 0;
23
24 // Sieve of Eratosthenes: iterate through numbers starting from 2
25 for (int i = 2; i < n; i++) {
26 // If current number is prime
27 if (isPrime[i]) {
28 primeCount++;
29
30 // Mark all multiples of i as non-prime
31 // Optimization: start from i * i instead of i + i
32 if ((long) i * i < n) {
33 for (int j = i * i; j < n; j += i) {
34 isPrime[j] = false;
35 }
36 }
37 }
38 }
39
40 return primeCount;
41 }
42}
43
1class Solution {
2public:
3 int countPrimes(int n) {
4 // Edge case: no primes less than 2
5 if (n <= 2) {
6 return 0;
7 }
8
9 // Initialize a boolean array to track prime numbers
10 // Initially assume all numbers are prime (true)
11 vector<bool> isPrime(n, true);
12
13 // 0 and 1 are not prime numbers
14 isPrime[0] = false;
15 isPrime[1] = false;
16
17 // Count of prime numbers found
18 int primeCount = 0;
19
20 // Sieve of Eratosthenes algorithm
21 for (int i = 2; i < n; ++i) {
22 // If current number is prime
23 if (isPrime[i]) {
24 // Increment prime counter
25 ++primeCount;
26
27 // Mark all multiples of i as non-prime
28 // Start from i*i as smaller multiples are already marked
29 // Optimization: cast i to long long to prevent overflow
30 for (long long j = (long long)i * i; j < n; j += i) {
31 isPrime[j] = false;
32 }
33 }
34 }
35
36 return primeCount;
37 }
38};
39
1/**
2 * Counts the number of prime numbers less than n using the Sieve of Eratosthenes algorithm
3 * @param n - The upper bound (exclusive) for counting prime numbers
4 * @returns The count of prime numbers less than n
5 */
6function countPrimes(n: number): number {
7 // Initialize a boolean array to track prime status, initially assuming all numbers are prime
8 const isPrime: boolean[] = new Array(n).fill(true);
9
10 // Counter for the number of primes found
11 let primeCount: number = 0;
12
13 // Iterate through numbers starting from 2 (the smallest prime)
14 for (let i = 2; i < n; i++) {
15 // If current number is marked as prime
16 if (isPrime[i]) {
17 // Increment the prime counter
18 primeCount++;
19
20 // Mark all multiples of i as non-prime (composite)
21 // Start from i + i (or 2i) and increment by i each time
22 for (let j = i + i; j < n; j += i) {
23 isPrime[j] = false;
24 }
25 }
26 }
27
28 return primeCount;
29}
30
Time and Space Complexity
Time Complexity: O(n log log n)
The algorithm implements the Sieve of Eratosthenes. The outer loop runs from 2 to n-1, which is O(n)
iterations. For each prime number i
, the inner loop marks its multiples as non-prime. The number of operations for marking multiples is:
- For prime 2:
n/2
operations - For prime 3:
n/3
operations - For prime 5:
n/5
operations - And so on for each prime
p
:n/p
operations
The total number of operations is n × (1/2 + 1/3 + 1/5 + 1/7 + ... + 1/p)
where p
is the largest prime less than n
. This sum of reciprocals of primes up to n
is approximately log log n
. Therefore, the overall time complexity is O(n log log n)
.
Space Complexity: O(n)
The algorithm uses a boolean array primes
of size n
to track whether each number is prime or not. This requires O(n)
space. The only other variables used are ans
and loop counters i
and j
, which require O(1)
additional space. Therefore, the total space complexity is O(n)
.
Common Pitfalls
1. Inefficient Multiple Marking - Starting from 2i Instead of i²
The current implementation marks multiples starting from current_num * 2
, but this creates unnecessary work. When we reach a prime i
, all its multiples less than i²
have already been marked by smaller primes.
Why this happens: For any composite number less than i²
, it must have a prime factor smaller than i
. For example, when processing prime 5, numbers like 10 (2×5), 15 (3×5), and 20 (4×5) have already been marked when processing primes 2 and 3.
Optimized Solution:
# Instead of:
for multiple in range(current_num * 2, n, current_num):
is_prime[multiple] = False
# Use:
for multiple in range(current_num * current_num, n, current_num):
is_prime[multiple] = False
2. Unnecessary Iteration - Processing All Numbers Up to n
The algorithm continues checking and marking multiples even for numbers whose square exceeds n
. Once i² > n
, all composite numbers less than n
have already been marked.
Optimized Solution:
# Add early termination when i² > n
for current_num in range(2, n):
if is_prime[current_num]:
prime_count += 1
# Only mark multiples if i² < n
if current_num * current_num < n:
for multiple in range(current_num * current_num, n, current_num):
is_prime[multiple] = False
3. Memory Optimization for Large n
Using a full boolean array for very large values of n
can consume significant memory. Each boolean typically takes 1 byte, so for n = 10^8
, the array uses ~100MB.
Alternative Approaches:
- Use a bitset or bit manipulation to reduce memory by 8x
- Implement a segmented sieve for extremely large ranges
- Consider space-time tradeoffs based on constraints
4. Integer Overflow in Multiple Calculation
In some languages (though not Python), calculating current_num * current_num
might cause integer overflow for large values.
Safe Implementation:
# Check if i² would exceed n before calculating
if current_num > int(n ** 0.5):
continue # No need to mark multiples
Complete Optimized Solution:
class Solution:
def countPrimes(self, n: int) -> int:
if n <= 2:
return 0
is_prime = [True] * n
is_prime[0] = is_prime[1] = False
# Only need to check up to sqrt(n)
for i in range(2, int(n ** 0.5) + 1):
if is_prime[i]:
# Mark multiples starting from i², with step i
for j in range(i * i, n, i):
is_prime[j] = False
# Count all remaining primes
return sum(is_prime)
This optimized version reduces unnecessary operations from O(n log log n) to a tighter bound, making it significantly faster for large inputs.
Which of the following is a min heap?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!