Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Math & Geometry
MediumLC 204

Count Primes (LeetCode 204)

Target Frequency:MetaAmazonGoogleApple

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.

Worked Examples

Example 1
Input:n = 10
Output:4
Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.
Example 2
Input:n = 0
Output:0
Explanation: There are no primes below 0.
Example 3
Input:n = 1
Output:0
Explanation: There are no primes below 1.

⚖️Formal Constraints & Bounds

  • 0 <= n <= 5 * 106

Deep-Dive & Conceptual Insights

Core Insight

Never trial divide every candidate up to n. Use a boolean sieve array where crossing off multiples starting at p^2 eliminates composites in O(N log log N) near-linear time.

Real-World Scenario & Production Applications

Cryptographic prime generation for RSA key exchanges, hashing algorithms, and pseudo-random prime factor generation.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Boolean Primality Array Initialization

Allocate a boolean array is_prime = [True] * n where is_prime[0] = is_prime[1] = False since 0 and 1 are not prime by definition.

Mathematical Recurrence / Code Invariant
is_prime = [False, False] + [True] * (n - 2) if n > 2 else [False] * n

Step-by-Step Execution Trace Table

Candidate Prime pIs is_prime[p] True?ActionMultiples Eliminated (p*p, p*p + p, ... < n=10)Sieve State (Indices 0..9)
2TrueEliminate multiples starting at 22=42^2=422=44, 6, 8[F, F, T, T, F, T, F, T, F, T]
3TrueEliminate multiples starting at 32=93^2=932=99[F, F, T, T, F, T, F, T, F, F]
≥10≈3.16\ge \sqrt{10} \approx 3.16≥10​≈3.16—Sieve loop terminates—Remaining True: 2, 3, 5, 7 (Count: 4)
Worked on Paper

The Math, Step by Step

1. The table the code fills

The code keeps a list is_prime of length nnn, one entry for each number from 0 to n−1n - 1n−1. At the end, is_prime[m] is True exactly when mmm is prime. The answer is the number of True entries, sum(is_prime).

A number m>1m > 1m>1 is composite when it is not prime, so it has a factor other than 1 and mmm. The code starts with every entry True, sets 0 and 1 to False, and then crosses out composite numbers: it sets their entries to False. This method is the Sieve of Eratosthenes.

2. What one prime ppp crosses out

For one prime ppp, the inner loop range(p * p, n, p) crosses out

p2, p2+p, p2+2p, …(every multiple of p from p2 up to n−1)p^2,\ p^2 + p,\ p^2 + 2p,\ \ldots \quad \text{(every multiple of } p \text{ from } p^2 \text{ up to } n - 1\text{)}p2, p2+p, p2+2p, …(every multiple of p from p2 up to n−1)

Each of these is ppp times a whole number of at least ppp, so each is composite.

3. Why starting at p2p^2p2 and stopping at n\sqrt{n}n​ loses nothing

Start at p2p^2p2. Take a multiple of ppp below p2p^2p2, say m=p⋅jm = p \cdot jm=p⋅j with 2≤j<p2 \le j < p2≤j<p. The number jjj has a prime factor q≤j<pq \le j < pq≤j<p. So mmm is a multiple of the smaller prime qqq, and the loop for qqq already crossed it out.

Stop at n\sqrt{n}n​. Every composite m<nm < nm<n can be written as m=a⋅bm = a \cdot bm=a⋅b with 2≤a≤b2 \le a \le b2≤a≤b. Then a⋅a≤a⋅b=m<na \cdot a \le a \cdot b = m < na⋅a≤a⋅b=m<n, so a<na < \sqrt{n}a<n​. The smallest prime factor ppp of mmm is at most aaa, so p<np < \sqrt{n}p<n​. Also m=p⋅(m/p)m = p \cdot (m / p)m=p⋅(m/p) with m/p≥pm / p \ge pm/p≥p, so m≥p2m \ge p^2m≥p2. So the loop for ppp crosses out mmm. Every composite below nnn is crossed out by a prime ppp with p≤np \le \sqrt{n}p≤n​. Primes above n\sqrt{n}n​ need no loop of their own.

The if is_prime[p] check. When the outer loop reaches ppp, every composite below p2p^2p2 is already crossed out, by the argument above. So is_prime[p] is True exactly when ppp is prime. A composite ppp is skipped, because its multiples are also multiples of its prime factors.

4. The bounds in the code

range(2, int(n ** 0.5) + 1) runs ppp up to ⌊n⌋\lfloor \sqrt{n} \rfloor⌊n​⌋, including it. The + 1 is needed because range stops one before its end. For LeetCode's inputs, n≤5⋅106n \le 5 \cdot 10^6n≤5⋅106, int(n ** 0.5) equals the exact integer square root for every nnn, so the float square root never cuts off a needed ppp.

The early return if n <= 2: return 0 covers the inputs with no prime below nnn. It also keeps is_prime[1] a valid index: the list needs at least two entries for the line is_prime[0] = is_prime[1] = False.

The problem asks for primes less than nnn, so nnn itself is never in the list. range(p * p, n, p) stops before nnn for the same reason.

5. Example 1 by hand

n = 10, so is_prime has entries 0 to 9, and ppp runs from 2 to ⌊10⌋=3\lfloor \sqrt{10} \rfloor = 3⌊10​⌋=3.

pppis_prime[p]Crossed out by range(p * p, 10, p)
2True4, 6, 8
3True9

After the loop, the entries still True are 2, 3, 5 and 7. The number 6 is a multiple of both 2 and 3. The loop for 3 starts at 9, so 6 is crossed out only once, by the loop for 2. The code returns 4, the expected output.

F0F1T2T3F4T5F6T7F8F9p=2p=2p=2p=3

The picture shows the ten entries of is_prime after the loop, one cell per number from 0 to 9, with the number under its cell. "T" is True and "F" is False. Entries 0 and 1 are False because the code sets them before the loop. The label "p=2" marks the entries 4, 6 and 8, which the loop for p=2p = 2p=2 crosses out. The label "p=3" marks entry 9, which the loop for p=3p = 3p=3 crosses out. The four entries still True are 2, 3, 5 and 7, and the final sum(is_prime) counts them.

6. Example 2 by hand

n = 0. The condition n <= 2 is true, so the code returns 0 at once, the expected output.

7. How much work the crossing out takes

The loop for a prime ppp writes at most n/pn / pn/p entries. So the total work is about nnn times the sum of 1/p1 / p1/p over the primes p≤np \le \sqrt{n}p≤n​. That sum grows like ln⁡ln⁡n\ln \ln nlnlnn, a known result about primes (Mertens' second theorem). So the sieve takes O(nlog⁡log⁡n)O(n \log \log n)O(nloglogn) time, and the list takes O(n)O(n)O(n) space. For n=106n = 10^6n=106, the loops write 212204621220462122046 entries, about 2.1 writes per number.

8. From the math to the code
MathIn countPrimes
no prime below 2if n <= 2: followed by return 0
one entry for each mmm from 0 to n−1n - 1n−1is_prime = [True] * n
0 and 1 are not primeis_prime[0] = is_prime[1] = False
ppp up to ⌊n⌋\lfloor \sqrt{n} \rfloor⌊n​⌋for p in range(2, int(n ** 0.5) + 1):
only primes cross outif is_prime[p]:
p2,p2+p,…p^2, p^2 + p, \ldotsp2,p2+p,… below nnnfor multiple in range(p * p, n, p):
count the primesreturn sum(is_prime)
Code Shape
Archetype Code Shape
Python
1if n <= 2:
2 return 0
3is_prime = [True] * n
4is_prime[0] = is_prime[1] = False
5for p in range(2, int(n ** 0.5) + 1):
6 if is_prime[p]:
7 mark every multiple of p, from p * p up, as not prime
8return sum(is_prime)

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Checking every number for primality one at a time (trial division) is the obvious approach — and also the slow one. This algorithm never tests whether a number is prime; it only crosses off numbers it can already prove are not.

📅 The Analogy: Crossing Off a Giant Calendar

Picture every integer from 2 to n written on a giant wall calendar, all initially assumed prime. Starting at 2, you cross off every multiple of 2 (4, 6, 8, ...) — none of those can possibly be prime, since they're all divisible by 2. The next number still standing, uncrossed, must be prime (nothing smaller crossed it off) — so you keep it, then cross off all of its multiples too. Repeat: keep the next survivor, cross off its multiples, and so on. Whatever is still standing uncrossed at the end was never a multiple of anything smaller than itself — which is the definition of prime.

🔑 The Key Idea in the Code
  • Trick 1 (start crossing at p * p, not 2 * p): Every smaller multiple of p (like 2p, 3p, ..., (p-1)p) was already crossed off earlier, by a smaller prime that divides it. p * p is the first multiple of p that no smaller prime could have already reached.
  • Trick 2 (stop at √n): Any composite number ≤ n must have a prime factor ≤ √n — otherwise multiplying its two smallest factors together would already exceed n. So once p passes √n, every remaining composite has already been crossed off by a smaller prime.
  • Problem asks for primes strictly less than nnn: The problem statement specifies primes < n, not ≤n\le n≤n. Allocating a sieve of size nnn and not counting index nnn is required.

  • Starting multiple marking loop from 2⋅p2 \cdot p2⋅p instead of p×pp \times pp×p: All composites smaller than p×pp \times pp×p that have ppp as a factor have already been marked by smaller prime factors. Starting the inner loop from p×pp \times pp×p avoids redundant iterations.

  • Edge cases n≤2n \le 2n≤2: There are 0 primes strictly less than 2. Forgetting to guard if n <= 2: return 0 causes out-of-bounds or off-by-one errors.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Counting primes strictly less than N, sub-quadratic prime enumeration without repeated trial division.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: Every composite number m < N has a prime factor p <= √m. Marking multiples pp, pp + p, ... eliminates all composites efficiently.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Clear is_prime[0] and is_prime[1] before sieving: no crossing reaches them, so n = 10 would count 6 instead of 4.

  • Primes strictly below n: the table is [True] * n (indices 0 to n - 1), and n <= 2 returns 0 first, since for n = 0 or 1 the table has no index 1 to clear.

  • The outer bound is int(n ** 0.5) + 1: without the + 1, p never reaches sqrt(n), so n = 5 counts 4 as prime.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use the Sieve of Eratosthenes, because I need an answer for every number below n, not just one. I make is_prime, a list of n booleans set to True, then set is_prime[0] and is_prime[1] to False. For each p from 2 up to the square root of n, if p is still marked prime, I cross off its multiples starting at p * p, stepping by p. Starting there is safe because a smaller multiple p * k with k < p was already crossed off by a prime factor of k, and stopping at the square root is safe because every composite below n has a prime factor no larger than its square root. The trap is 0 and 1: no crossing ever reaches them, so if I don't set them to False first, the count comes out two too high. Summing is_prime gives the answer in O(N log log N) time and O(N) space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N log log N)

Look at the nested loop: The outer loop runs for prime p <= sqrt(n). For each prime p, the inner loop marks multiples p^2, p^2 + p, p^2 + 2p... up to n. The total operations equal n times the sum of 1/p over every prime p <= sqrt(n). By Mertens' Second Theorem, the sum of reciprocals of primes diverges as log(log N). Multiplying by n yields strictly O(N log log N) time.

SPACE COMPLEXITY

O(N)

Look at our memory allocations: We allocate a contiguous boolean array is_prime of length n. The call stack uses O(1) iterative space. Auxiliary memory is strictly O(N) bytes.

Formal Recurrence Relation

T(N)=N∑p≤N1p=O(Nlog⁡log⁡N)T(N) = N \sum_{p \le \sqrt{N}} \frac{1}{p} = O(N \log \log N)T(N)=N∑p≤N​​p1​=O(NloglogN)

Derivation Progression

Inner Multiple Stride

Np−p iterations for prime p\frac{N}{p} - p \text{ iterations for prime } ppN​−p iterations for prime p

Multiples smaller than p^2 have already been marked by smaller prime factors, so inner loop begins directly at p^2.

Prime Harmonic Sum

∑p≤N1p∼ln⁡(ln⁡N)=O(log⁡log⁡N)\sum_{p \le \sqrt{N}} \frac{1}{p} \sim \ln(\ln \sqrt{N}) = O(\log \log N)∑p≤N​​p1​∼ln(lnN​)=O(loglogN)

The sum of 1/p over the primes never converges: by Mertens' second theorem it grows like log log N, which is why the total is only a small factor above N.

Total Runtime

N⋅O(log⁡log⁡N)=O(Nlog⁡log⁡N)N \cdot O(\log \log N) = O(N \log \log N)N⋅O(loglogN)=O(NloglogN)

Near-linear time complexity that evaluates millions of integers in milliseconds.

Variable Definitions

NNN

Upper bound integer limit

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative double for-loop

🔵 Auxiliary Heap

O(N) Contiguous boolean array is_prime of size N

🟢 Output Space

O(1) Returns scalar count of primes

Boundary Best / Worst Cases

Best Case

O(1)O(1)O(1) for N <= 2.

Average Case

O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN).

Worst Case

O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN) full sieve pass.

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Trigger phrase: "count the number of prime numbers strictly less than n". Naive trial division for each integer k∈[2,n−1]k \in [2, n-1]k∈[2,n−1] takes O(NN)O(N \sqrt{N})O(NN​), which executes 5×106×2236≈1.1×10105 \times 10^6 \times 2236 \approx 1.1 \times 10^{10}5×106×2236≈1.1×1010 operations and inevitably causes Time Limit Exceeded. A Senior SWE immediately applies the Sieve of Eratosthenes.

CONSTRAINTS & BOUNDS

0≤n≤5×1060 \le n \le 5 \times 10^60≤n≤5×106. Time budget must be sub-O(NN)O(N \sqrt{N})O(NN​); the sieve's O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN) is only a small factor above NNN. Memory is the real limit: [True] * n holds one 8-byte pointer per slot in CPython (about 40 MB for n=5×106n = 5 \times 10^6n=5×106), while a bytearray(n) needs one byte per slot (about 5 MB).

FAANG PRODUCTION TRAPS & EDGE CASES

The outer prime scan only needs to run up to ⌊n⌋\lfloor\sqrt{n}\rfloor⌊n​⌋; running it to nnn stays correct but wastes checks. For very large nnn, int(n ** 0.5) goes through a float and can round the wrong way; math.isqrt(n) is always exact.

Core Algorithmic State Invariants

  1. Memory Is the Limit at Scale

For n = 5 * 10^6 the time is small, but a Python list of booleans stores an 8-byte pointer per slot (about 40 MB). A bytearray stores one byte per slot (about 5 MB), and past one machine's memory a segmented sieve crosses one block at a time with the primes up to sqrt(n).

Theory Context•Math & Geometry
MediumLC 204

Count Primes (LeetCode 204)

Target Frequency:MetaAmazonGoogleApple

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.

Worked Examples

Example 1
Input:n = 10
Output:4
Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.
Example 2
Input:n = 0
Output:0
Explanation: There are no primes below 0.
Example 3
Input:n = 1
Output:0
Explanation: There are no primes below 1.

⚖️Formal Constraints & Bounds

  • 0 <= n <= 5 * 106

Deep-Dive & Conceptual Insights

Core Insight

Never trial divide every candidate up to n. Use a boolean sieve array where crossing off multiples starting at p^2 eliminates composites in O(N log log N) near-linear time.

Real-World Scenario & Production Applications

Cryptographic prime generation for RSA key exchanges, hashing algorithms, and pseudo-random prime factor generation.

Subproblems & Recurrence Decomposition3 Phases

🧩Subproblem 1: Boolean Primality Array Initialization

Allocate a boolean array is_prime = [True] * n where is_prime[0] = is_prime[1] = False since 0 and 1 are not prime by definition.

Mathematical Recurrence / Code Invariant
is_prime = [False, False] + [True] * (n - 2) if n > 2 else [False] * n

Step-by-Step Execution Trace Table

Candidate Prime pIs is_prime[p] True?ActionMultiples Eliminated (p*p, p*p + p, ... < n=10)Sieve State (Indices 0..9)
2TrueEliminate multiples starting at 22=42^2=422=44, 6, 8[F, F, T, T, F, T, F, T, F, T]
3TrueEliminate multiples starting at 32=93^2=932=99[F, F, T, T, F, T, F, T, F, F]
≥10≈3.16\ge \sqrt{10} \approx 3.16≥10​≈3.16—Sieve loop terminates—Remaining True: 2, 3, 5, 7 (Count: 4)
Worked on Paper

The Math, Step by Step

1. The table the code fills

The code keeps a list is_prime of length nnn, one entry for each number from 0 to n−1n - 1n−1. At the end, is_prime[m] is True exactly when mmm is prime. The answer is the number of True entries, sum(is_prime).

A number m>1m > 1m>1 is composite when it is not prime, so it has a factor other than 1 and mmm. The code starts with every entry True, sets 0 and 1 to False, and then crosses out composite numbers: it sets their entries to False. This method is the Sieve of Eratosthenes.

2. What one prime ppp crosses out

For one prime ppp, the inner loop range(p * p, n, p) crosses out

p2, p2+p, p2+2p, …(every multiple of p from p2 up to n−1)p^2,\ p^2 + p,\ p^2 + 2p,\ \ldots \quad \text{(every multiple of } p \text{ from } p^2 \text{ up to } n - 1\text{)}p2, p2+p, p2+2p, …(every multiple of p from p2 up to n−1)

Each of these is ppp times a whole number of at least ppp, so each is composite.

3. Why starting at p2p^2p2 and stopping at n\sqrt{n}n​ loses nothing

Start at p2p^2p2. Take a multiple of ppp below p2p^2p2, say m=p⋅jm = p \cdot jm=p⋅j with 2≤j<p2 \le j < p2≤j<p. The number jjj has a prime factor q≤j<pq \le j < pq≤j<p. So mmm is a multiple of the smaller prime qqq, and the loop for qqq already crossed it out.

Stop at n\sqrt{n}n​. Every composite m<nm < nm<n can be written as m=a⋅bm = a \cdot bm=a⋅b with 2≤a≤b2 \le a \le b2≤a≤b. Then a⋅a≤a⋅b=m<na \cdot a \le a \cdot b = m < na⋅a≤a⋅b=m<n, so a<na < \sqrt{n}a<n​. The smallest prime factor ppp of mmm is at most aaa, so p<np < \sqrt{n}p<n​. Also m=p⋅(m/p)m = p \cdot (m / p)m=p⋅(m/p) with m/p≥pm / p \ge pm/p≥p, so m≥p2m \ge p^2m≥p2. So the loop for ppp crosses out mmm. Every composite below nnn is crossed out by a prime ppp with p≤np \le \sqrt{n}p≤n​. Primes above n\sqrt{n}n​ need no loop of their own.

The if is_prime[p] check. When the outer loop reaches ppp, every composite below p2p^2p2 is already crossed out, by the argument above. So is_prime[p] is True exactly when ppp is prime. A composite ppp is skipped, because its multiples are also multiples of its prime factors.

4. The bounds in the code

range(2, int(n ** 0.5) + 1) runs ppp up to ⌊n⌋\lfloor \sqrt{n} \rfloor⌊n​⌋, including it. The + 1 is needed because range stops one before its end. For LeetCode's inputs, n≤5⋅106n \le 5 \cdot 10^6n≤5⋅106, int(n ** 0.5) equals the exact integer square root for every nnn, so the float square root never cuts off a needed ppp.

The early return if n <= 2: return 0 covers the inputs with no prime below nnn. It also keeps is_prime[1] a valid index: the list needs at least two entries for the line is_prime[0] = is_prime[1] = False.

The problem asks for primes less than nnn, so nnn itself is never in the list. range(p * p, n, p) stops before nnn for the same reason.

5. Example 1 by hand

n = 10, so is_prime has entries 0 to 9, and ppp runs from 2 to ⌊10⌋=3\lfloor \sqrt{10} \rfloor = 3⌊10​⌋=3.

pppis_prime[p]Crossed out by range(p * p, 10, p)
2True4, 6, 8
3True9

After the loop, the entries still True are 2, 3, 5 and 7. The number 6 is a multiple of both 2 and 3. The loop for 3 starts at 9, so 6 is crossed out only once, by the loop for 2. The code returns 4, the expected output.

F0F1T2T3F4T5F6T7F8F9p=2p=2p=2p=3

The picture shows the ten entries of is_prime after the loop, one cell per number from 0 to 9, with the number under its cell. "T" is True and "F" is False. Entries 0 and 1 are False because the code sets them before the loop. The label "p=2" marks the entries 4, 6 and 8, which the loop for p=2p = 2p=2 crosses out. The label "p=3" marks entry 9, which the loop for p=3p = 3p=3 crosses out. The four entries still True are 2, 3, 5 and 7, and the final sum(is_prime) counts them.

6. Example 2 by hand

n = 0. The condition n <= 2 is true, so the code returns 0 at once, the expected output.

7. How much work the crossing out takes

The loop for a prime ppp writes at most n/pn / pn/p entries. So the total work is about nnn times the sum of 1/p1 / p1/p over the primes p≤np \le \sqrt{n}p≤n​. That sum grows like ln⁡ln⁡n\ln \ln nlnlnn, a known result about primes (Mertens' second theorem). So the sieve takes O(nlog⁡log⁡n)O(n \log \log n)O(nloglogn) time, and the list takes O(n)O(n)O(n) space. For n=106n = 10^6n=106, the loops write 212204621220462122046 entries, about 2.1 writes per number.

8. From the math to the code
MathIn countPrimes
no prime below 2if n <= 2: followed by return 0
one entry for each mmm from 0 to n−1n - 1n−1is_prime = [True] * n
0 and 1 are not primeis_prime[0] = is_prime[1] = False
ppp up to ⌊n⌋\lfloor \sqrt{n} \rfloor⌊n​⌋for p in range(2, int(n ** 0.5) + 1):
only primes cross outif is_prime[p]:
p2,p2+p,…p^2, p^2 + p, \ldotsp2,p2+p,… below nnnfor multiple in range(p * p, n, p):
count the primesreturn sum(is_prime)
Code Shape
Archetype Code Shape
Python
1if n <= 2:
2 return 0
3is_prime = [True] * n
4is_prime[0] = is_prime[1] = False
5for p in range(2, int(n ** 0.5) + 1):
6 if is_prime[p]:
7 mark every multiple of p, from p * p up, as not prime
8return sum(is_prime)

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Checking every number for primality one at a time (trial division) is the obvious approach — and also the slow one. This algorithm never tests whether a number is prime; it only crosses off numbers it can already prove are not.

📅 The Analogy: Crossing Off a Giant Calendar

Picture every integer from 2 to n written on a giant wall calendar, all initially assumed prime. Starting at 2, you cross off every multiple of 2 (4, 6, 8, ...) — none of those can possibly be prime, since they're all divisible by 2. The next number still standing, uncrossed, must be prime (nothing smaller crossed it off) — so you keep it, then cross off all of its multiples too. Repeat: keep the next survivor, cross off its multiples, and so on. Whatever is still standing uncrossed at the end was never a multiple of anything smaller than itself — which is the definition of prime.

🔑 The Key Idea in the Code
  • Trick 1 (start crossing at p * p, not 2 * p): Every smaller multiple of p (like 2p, 3p, ..., (p-1)p) was already crossed off earlier, by a smaller prime that divides it. p * p is the first multiple of p that no smaller prime could have already reached.
  • Trick 2 (stop at √n): Any composite number ≤ n must have a prime factor ≤ √n — otherwise multiplying its two smallest factors together would already exceed n. So once p passes √n, every remaining composite has already been crossed off by a smaller prime.
  • Problem asks for primes strictly less than nnn: The problem statement specifies primes < n, not ≤n\le n≤n. Allocating a sieve of size nnn and not counting index nnn is required.

  • Starting multiple marking loop from 2⋅p2 \cdot p2⋅p instead of p×pp \times pp×p: All composites smaller than p×pp \times pp×p that have ppp as a factor have already been marked by smaller prime factors. Starting the inner loop from p×pp \times pp×p avoids redundant iterations.

  • Edge cases n≤2n \le 2n≤2: There are 0 primes strictly less than 2. Forgetting to guard if n <= 2: return 0 causes out-of-bounds or off-by-one errors.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

Counting primes strictly less than N, sub-quadratic prime enumeration without repeated trial division.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Invariant: Every composite number m < N has a prime factor p <= √m. Marking multiples pp, pp + p, ... eliminates all composites efficiently.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Clear is_prime[0] and is_prime[1] before sieving: no crossing reaches them, so n = 10 would count 6 instead of 4.

  • Primes strictly below n: the table is [True] * n (indices 0 to n - 1), and n <= 2 returns 0 first, since for n = 0 or 1 the table has no index 1 to clear.

  • The outer bound is int(n ** 0.5) + 1: without the + 1, p never reaches sqrt(n), so n = 5 counts 4 as prime.

The 60-Second Interview Pitch

Say this out loud before you type a single line

"I'd use the Sieve of Eratosthenes, because I need an answer for every number below n, not just one. I make is_prime, a list of n booleans set to True, then set is_prime[0] and is_prime[1] to False. For each p from 2 up to the square root of n, if p is still marked prime, I cross off its multiples starting at p * p, stepping by p. Starting there is safe because a smaller multiple p * k with k < p was already crossed off by a prime factor of k, and stopping at the square root is safe because every composite below n has a prime factor no larger than its square root. The trap is 0 and 1: no crossing ever reaches them, so if I don't set them to False first, the count comes out two too high. Summing is_prime gives the answer in O(N log log N) time and O(N) space."

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N log log N)

Look at the nested loop: The outer loop runs for prime p <= sqrt(n). For each prime p, the inner loop marks multiples p^2, p^2 + p, p^2 + 2p... up to n. The total operations equal n times the sum of 1/p over every prime p <= sqrt(n). By Mertens' Second Theorem, the sum of reciprocals of primes diverges as log(log N). Multiplying by n yields strictly O(N log log N) time.

SPACE COMPLEXITY

O(N)

Look at our memory allocations: We allocate a contiguous boolean array is_prime of length n. The call stack uses O(1) iterative space. Auxiliary memory is strictly O(N) bytes.

Formal Recurrence Relation

T(N)=N∑p≤N1p=O(Nlog⁡log⁡N)T(N) = N \sum_{p \le \sqrt{N}} \frac{1}{p} = O(N \log \log N)T(N)=N∑p≤N​​p1​=O(NloglogN)

Derivation Progression

Inner Multiple Stride

Np−p iterations for prime p\frac{N}{p} - p \text{ iterations for prime } ppN​−p iterations for prime p

Multiples smaller than p^2 have already been marked by smaller prime factors, so inner loop begins directly at p^2.

Prime Harmonic Sum

∑p≤N1p∼ln⁡(ln⁡N)=O(log⁡log⁡N)\sum_{p \le \sqrt{N}} \frac{1}{p} \sim \ln(\ln \sqrt{N}) = O(\log \log N)∑p≤N​​p1​∼ln(lnN​)=O(loglogN)

The sum of 1/p over the primes never converges: by Mertens' second theorem it grows like log log N, which is why the total is only a small factor above N.

Total Runtime

N⋅O(log⁡log⁡N)=O(Nlog⁡log⁡N)N \cdot O(\log \log N) = O(N \log \log N)N⋅O(loglogN)=O(NloglogN)

Near-linear time complexity that evaluates millions of integers in milliseconds.

Variable Definitions

NNN

Upper bound integer limit

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative double for-loop

🔵 Auxiliary Heap

O(N) Contiguous boolean array is_prime of size N

🟢 Output Space

O(1) Returns scalar count of primes

Boundary Best / Worst Cases

Best Case

O(1)O(1)O(1) for N <= 2.

Average Case

O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN).

Worst Case

O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN) full sieve pass.

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Trigger phrase: "count the number of prime numbers strictly less than n". Naive trial division for each integer k∈[2,n−1]k \in [2, n-1]k∈[2,n−1] takes O(NN)O(N \sqrt{N})O(NN​), which executes 5×106×2236≈1.1×10105 \times 10^6 \times 2236 \approx 1.1 \times 10^{10}5×106×2236≈1.1×1010 operations and inevitably causes Time Limit Exceeded. A Senior SWE immediately applies the Sieve of Eratosthenes.

CONSTRAINTS & BOUNDS

0≤n≤5×1060 \le n \le 5 \times 10^60≤n≤5×106. Time budget must be sub-O(NN)O(N \sqrt{N})O(NN​); the sieve's O(Nlog⁡log⁡N)O(N \log \log N)O(NloglogN) is only a small factor above NNN. Memory is the real limit: [True] * n holds one 8-byte pointer per slot in CPython (about 40 MB for n=5×106n = 5 \times 10^6n=5×106), while a bytearray(n) needs one byte per slot (about 5 MB).

FAANG PRODUCTION TRAPS & EDGE CASES

The outer prime scan only needs to run up to ⌊n⌋\lfloor\sqrt{n}\rfloor⌊n​⌋; running it to nnn stays correct but wastes checks. For very large nnn, int(n ** 0.5) goes through a float and can round the wrong way; math.isqrt(n) is always exact.

Core Algorithmic State Invariants

  1. Memory Is the Limit at Scale

For n = 5 * 10^6 the time is small, but a Python list of booleans stores an 8-byte pointer per slot (about 40 MB). A bytearray stores one byte per slot (about 5 MB), and past one machine's memory a segmented sieve crosses one block at a time with the primes up to sqrt(n).

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•