Problem
Count how many prime numbers exist that are strictly less than a given integer n. A prime is any integer greater than 1 that is divisible only by 1 and itself.
- Input:
n = 10 - Output:
4 - Explanation: The primes less than 10 are 2, 3, 5, and 7.
Counter-example: n = 2 โ output 0. There are no primes below 2, since 2 itself is not "strictly less than" 2.
Intuition
For a small input you could test each number individually, but verifying every candidate up to n is expensive. The key insight is that once you confirm a number p is prime, every multiple of p โ other than p itself โ is guaranteed composite. Instead of asking "is this number prime?" one at a time, you can batch-eliminate all composites at once. Starting that elimination at pยฒ (not 2p) is the critical optimization: every smaller multiple k ร p where k < p was already crossed out when you processed the prime k.
Approach 1 โ Brute Force
For each candidate from 2 to n โ 1, trial-divide it by every integer up to its square root. If no divisor divides evenly, the candidate is prime.
- Define a helper that checks if
numis prime - Loop
divisorfrom 2 to floor(โnum); return False on any clean division - If no divisor divides evenly, return True
- Count each prime candidate in
range(2, n)and return the total
1def countPrimes(n: int) -> int:
2 def is_prime(num):
3 if num < 2:
4 return False
5 # only check up to sqrt(num): any factor above sqrt pairs with a smaller factor below it
6 for divisor in range(2, int(num**0.5) + 1):
7 if num % divisor == 0:
8 return False
9 return True
10
11 count = 0
12 for candidate in range(2, n): # range stops before n: problem asks for primes strictly less than n
13 if is_prime(candidate):
14 count += 1
15 return countTime: O(nโn) โ for each of the n candidates we do up to โn division checks.
Space: O(1) โ just a counter; no extra array.
Approach 2 โ Sieve of Eratosthenes
Allocate a boolean array for every integer below n, initialize all to "prime", then cross off composites in bulk each time a prime is found.
- Return 0 immediately if
n โค 2โ no primes exist below 2 - Initialize
is_prime[0..n-1]to True; set indices 0 and 1 to False - For
ifrom 2 whilei * i < n: - If
is_prime[i]is True, mark every multiple ofistarting ati * ias False - Count and return all remaining True entries
1def countPrimes(n: int) -> int:
2 if n <= 2:
3 return 0
4
5 is_prime = [True] * n
6 is_prime[0] = is_prime[1] = False # 0 and 1 are not prime by definition
7
8 i = 2
9 while i * i < n: # composites with smallest prime factor > sqrt(n) are already handled
10 if is_prime[i]:
11 # start at i*i: multiples 2i, 3i, ..., (i-1)*i were already marked by smaller primes
12 for multiple in range(i * i, n, i):
13 is_prime[multiple] = False
14 i += 1
15
16 return sum(is_prime) # True counts as 1, False as 0Time: O(n log log n) โ total elimination work sums the harmonic series over primes, which converges to log log n.
Space: O(n) โ the boolean sieve stores one entry per integer below n.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Brute Force | O(nโn) | O(1) | Only when n is tiny (โค 10,000) and allocating O(n) memory is forbidden |
| Sieve of Eratosthenes | O(n log log n) | O(n) | Standard answer for interview; handles n up to ~10^7 comfortably |
Common Mistakes
- Including n itself: using
range(2, n + 1)or checkingcandidate <= ncounts primes โค n instead of < n โ the problem says strictly less than n, so n must be excluded. - Starting the inner sieve loop at
i * 2instead ofi * i: every multiplek ร iwherek < iwas already crossed off when the primekwas processed, so restarting at2ire-marks the same slots and adds unnecessary work. - Missing the
n โค 2edge case: for n = 0 or n = 1, there are no valid candidates; without an early return, attempting to setis_prime[0] = is_prime[1] = Falseon a zero- or one-element array causes an index error. - In the brute force, checking divisors up to
num - 1instead ofsqrt(num): ifd > sqrt(num)dividesnum, thennum / d < sqrt(num)is a smaller factor that would already have been caught โ checking past the square root is pure wasted work and turns O(nโn) into O(nยฒ). - Integer overflow on
i * iin Java and C++: for large n, the producti * ican silently overflow a 32-bit int; casting tolong/long longbefore the comparison ((long) i * i < n) prevents the outer loop from terminating too early.
Related Problems
Valid Perfect Squareโ determine a number property using math reasoning without calling built-in sqrt; same spirit of reasoning about divisibilityHappy Numberโ repeatedly apply a numeric function and detect when a cycle or target is reached; shares the number-theory flavorMissing Numberโ exploit a mathematical property (Gauss sum or XOR) to find an absent value; similar pattern of using math rather than brute-force lookupFind the Duplicate Numberโ uses mathematical insight (Floyd's cycle detection / pigeonhole) rather than checking each element independentlyReverse Integerโ handles the same integer overflow pitfall when processing digit-level operations; good companion for the overflow awareness this problem teaches