判断质数
本文题目难度标识:🟩简单,🟨中等,🟥困难。
判断一个数是否为质数
给定一个正整数 n ,判断它是不是素数。
站内文章质数 的定义:在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。
暴力枚举
因此对于每个数 x,我们可以从小到大枚举 中的每个数 y,判断 y 是否为 x 的因数。
1 | // 未优化解法 |
复杂度分析:
- 时间复杂度:
- 空间复杂度:
平方根优化
考虑到如果 y 是 x 的因数,那么 也必然是 x 的因数,因此我们只要校验 y 或者 即可。而如果我们每次选择校验两者中的较小数,则不难发现较小数一定落在 的区间中,因此我们只需要枚举 中的所有数即可。
1 | // 优化解法 |
复杂度分析:
- 时间复杂度:
- 空间复杂度:
6 倍原理优化
但是与 6 的倍数相邻的不一定是素数,有可能是 6 倍邻数的倍数,例如 25、35、49,这些数就分别是 5,7 的倍数。
大于等于 5 的素数必定为 6 倍邻数证明:
6 倍以外的数分别有:,,,,
其中 ,, 三个数都可以分解:
所以以上三个数必不可能是素数,剩下的只有 , 可能存在素数。
在判断 和 是否为素数过程中只需判断这个数是否为 6 倍邻数的倍数即可断定。
- 首先,, 为偶数,不可能是 , 这个两个奇数的因数,自然就不用判断;
- 然后,,所以 必为 3 的倍数,但是 , 并不会是 3 的倍数。原因:
- 故 和 ,要么是素数,要么为 和 的倍数。
利用这个性质我们还可以进一步优化代码:
- 对 1~3 进行特判;
- 先判断是否为 6 倍邻数,如果是则执行第二步;
- 判断这个数是否为 6 倍邻数的倍数,如若不是则这个数为素数。
1 | public static boolean isPrime(int num) { |
多次判断质数
给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
这道题的特点在于我们需要求出一组数是否是质数。如果对于每个数都执行 isPrime
方法,那么时间复杂度会达到 。
下面介绍两种筛法。
埃氏筛
大埃拉托斯特尼筛法用来寻找不超过一个给定整数的所有素数。
考虑以下事实:如果 x 是质数,那么大于 x 的 x 的倍数 2x,3x,… 一定不是质数。
- 例如寻找不超过 100 的素数:
- 不超过 100 的合数必定有一个不超过 10 的素因子。
- 小于 10 的素数有 2,3,5,7。
- 除了 2,3,5,7 以外,删除所有能被 2,3,5 或 7 整除的整数。
- 除了 1 以外,所有保留下来的都是素数。
我们设置 flags
数组,flags[i]==1
说明数 i
被标记,是合数,反之为质数。
1 | // 未优化版本 |
接下来我们还可以进行优化。对于一个质数 x
,如果按上文说的我们从 2x
开始标记其实是冗余的,应该直接从 x⋅x
开始标记,因为 2x
,3x
,… 这些数一定在 x
之前就被其他数的倍数标记过了,例如 2x
这个数,在外层循环 i==2
时已经被标记。
1 | // 优化版本 |
复杂度分析:
- 时间复杂度::本质上就是求解 的和,其中 p 为质数。
- 一个比较松的上界为 ,相当于考虑 的和,而 ,且 1 到 n 所有数的倒数和趋近于 。
- 空间复杂度:
线性筛
在埃氏筛中还是存在冗余的标记操作,比如对于 45 这个数,它会同时被 3,5 两个数标记为合数。我们的目标是每个合数只被标记一次,而且是只被自己的最小因数标记。
相较于埃氏筛,我们多维护一个 primes
数组表示当前得到的质数集合。我们从小到大遍历,如果当前的数 x 是质数,就将其加入 primes
数组。
「标记过程」不再仅当 x 为质数时才进行,而是对每个整数 x 都进行。对于整数 x,我们不再标记其所有的倍数 x⋅x
,x⋅(x+1)
,…,而是只标记质数集合中的数与 x 相乘的数,即 x⋅primes_0
,x⋅primes_1
,…,且在发现 x mod primes_i==0
时终止标记。
为什么发现 x mod primes_i==0
时终止后续数的标记?因为 x mod primes_i==0
,说明 x
本身携带着一个比 primes_i
更小(或相等)的因数 e
,e=x/primes_i
。如果我们不在此刻终止,而继续将 x⋅primes_{i+1}
标记为合数,但是这个合数的最小因子是 e
,在前面已经被 e
标记过了,我们不应当重复标记,不然就会违反之前「合数只被自己最小因数标记」的规定。
1 | public int countPrimes(int n) { |
复杂度分析:
- 时间复杂度:
- 空间复杂度:
本文参考
- 研究生《离散数学》课程笔记
- 相关 LeetCode 题题解
- 欧拉筛【力扣周赛 326】LeetCode_哔哩哔哩_bilibili
- 数论基础 - OI Wiki
- 高效判断素数算法(6倍原理)_为什么说大于5的素数必然出现在6的倍数两侧-CSDN博客