Count Prime Numbers
Given an integer n
, return the number of prime numbers that are strictly less than n
.
Example 1:
1 | Input: n = 10 |
Terminology
A prime number (or a prime) is a natural number greater than 1 that is not a product of two smaller natural numbers. A natural number greater than 1 that is not prime is called a composite number.1
Bruce Force
Brute force: 写一个判断素数的方法, 然后调用n次
1 | int countPrimes(int n) { |
Complexity: 1^2 + 2^2 + 3^2 + ... + (n-1)^ =
Sieve of Eratosthenes
Idea: 与其把{2, ..., n-1}的素数一个个找出, 不如{2, ..., n-1}的非素数全部去除, 剩下的都是素数.
例如:
- 2 是一个素数,那么 2 × 2 = 4, 3 × 2 = 6, 4 × 2 = 8... 都不可能是素数了.
- 3 也是素数,那么 3 × 2 = 6, 3 × 3 = 9, 3 × 4 = 12... 也都不可能是素数了.
这算法叫做 Sieve of Eratosthenes2.
Proof
注意到如果p是素数, 那么2 * p, 3 * p, ..., k * p 都是合数(composite number), 设这种方法构造出的集合为
Then we have:
由于质数分解定理, 所有的合数都可以用这种方法构造, 因此:
所以, 全体合数都可以用这种方法构造, 且这种方法构造出的数也都属于合数.
自然, 这个定律对小于n的合数也成立. 因此 小于n的全体合数都可以用这种方法构造, 且这种方法构造出的数也都属于小于n的合数.
Code
第一版代码:
1 | public int countPrimes(int n) { |
该代码还有两个可改进之处.
Improvement1
我们有如下定理:
- 对于任意的数字
, 有 , 且易知 , 至少有一个小于等于 . - 如果m是合数, 那么它可以写成若干个质数相乘的形式(即必定存在质因子):
Prime factorization: 每个合数都可以写成几个质数相乘的形式,其中每个质数都是这个合数的因数.
我们需要证明: For any composite number
也就是说合数
Proof
The prime factorization of compisite number
Let
then
If
, that's fine.If
, then we must have .Since
, we can swap the and any .So that the new
satisfies .
To sum, For any composite number
Implementation
下面这段代码的作用是“寻找合数m”, 它只需要找到一个
1 | //Remove all composite numbers that are multiples of p and less than n. |
而根据上面的证明, 任何合数
Since
1 | //Remove all composite numbers that are multiples of p and less than n. |
Code
1 | public int countPrimes(int n) { |
Improvement2
代码内层的 for 循环也可以优化. 我们之前的做法是:
1 | for(int m = 2 * p; m < n; m+=p) |
这样可以把 p
的整数倍都标记为 false
, 但是仍然存在计算冗余.
对于每个质数p, 我们只需从m = p * p
而不是m = 2 * p
开始迭代. 因为对于m = kp
已经在之前的p = k
时被迭代过了.
例如对于 n = 25
, p = 5
的情况, 算法会标记 5 × 2 = 10,5 × 3 = 15 等等数字, 但 5 × 2和 5 × 3已经在之前p = 2, p = 3
时被迭代过了.
Code
1 | for(int m = p * p; m < n; m+=p) |
Final Code
1 | public int countPrimes(int n) { |