✨前言✨
在这个系列中,博主准备分享每日在万人千题社区打卡学习的算法。博主也是小白,因此也很能理解新手在刷题时的困惑,所以关注博主,每天学习一道算法吧。同时也欢迎大家加入万人千题习活动,正所谓:一个人可以走的很快,但一群人才能走的更远。
万人千题社区https://bbs.csdn.net/forums/hero?category=0&typeId=17913https://bbs.csdn.net/forums/hero?category=0&typeId=17913
目录
✨前言✨
一、算法思想笔记
①枚举法:
②埃氏筛法:
③欧拉筛法
二、计算质数
①题目呈现
三、解决方法
①枚举法
②埃氏筛法
③欧拉筛法
一、算法思想笔记
①枚举法:
1.思想:一一枚举从2~n-1之间可能被n整除的数
int is_prime(int n)
{
int i = 0;
for (i = 2; i < n ; i++)
{
return 0;//能被2~n之间的数整除说明是合数,所以直接返回0
}
return 1;
}
优化一:排除所有除2外的偶数
int is_prime(int n)
{
int i = 2;
if (n % 2 == 0)
return 0;
for (i = 3; i < n ; i+=2)
{
return 0;
}
return 1;
}
优化二:枚举到 i*i<=n时即可终止遍历 (时间复杂度:O(√n))
解释:设A=B*C(2<=B
int is_prime(int n) { int i = 2; for (i = 2; i * i <= n; i++) { if (n % 2 == 0) return 0; } return 1; }【注意】如果写成 i<=(int)sqrt(n),要考虑到浮点数的误差 ,应该写成 i<=(int)(sqrt(n)+1E-6),或者可以写成i<=(int)floor(sqrt(n)+0.5)
②埃氏筛法:
时间复杂度:
1.思想:
总而言之,就是第一次筛去2所有的倍数, 第二次筛去3的倍数,第三次筛去5的倍数以此类推。
(代码在题目中呈现)
③欧拉筛法
时间复杂度:O(n)
1.背景:
埃氏筛存在对一个数重复筛的缺陷,比如30=2*15=5*6,这样30既可以被2筛,又会被5筛,造成了不必要的浪费。而欧拉筛的出现解决了这个问题。
2.数学原理
不言而喻,每一个数的最小因数肯定是确定的。假设A=B*C,如果我们确定了A的最小的因数B,只要C不同,那么得到的A必然不同。换句话说,我们以这种方式可以保证A只被筛一次。
(代码在题目中呈现)
二、计算质数
204. 计数质数https://leetcode-cn.com/problems/count-primes/https://leetcode-cn.com/problems/count-primes/
①题目呈现
三、解决方法
①枚举法
显然这里用枚举法会超出时间限制,不是一个好办法,那我们也试一试吧
int is_prime(int n) { int i = 0; for (i = 2; i * i <= n; i++) { if (n % i == 0) return 0; } return 1; } int countPrimes(int n) { int cnt = 0; if( n > 2) cnt++; for (int i = 3; i < n; i+=2) { if (is_prime(i)) cnt++; } return cnt; }运行结果:
②埃氏筛法
int countPrimes(int n) { if(n<3) return 0; int i; int cnt = 0; long long j; bool f[n]; memset(f, 0, sizeof(bool) * n); f[0] = 1;f[1] = 1; for (i = 2; i < n; ++i) { if (!f[i]) { ++cnt; for (j = (long long)i * i; j < n; j += i) { f[j] = 1; } } } return cnt; }1.算法技巧
每次 j 从 i * i 开始,为什么呢?我们可以试想 2*i 3*i ……(i-1)*i 分别被2 ,3……i-1筛过,所以没有必要再次筛。
2.核心算法思想:
①创建一个布尔类型的数组,f[n-1]存放着数字n-1是否为质数的真假值,以此类推,就是我们之前学过的桶排序 没看过?点这里
②若i为质数,内循环的j则用来筛去所有i的倍数,将他们的f[ ]值标记为1,表示不是质数
③在判断质数得同时cnt++,实现计数
3.本段代码注意点
①若n<3,则n最大为2,肯定不存在质数。并且这里不排除n<3的情况,之后f[0],f[1]的赋值会出现数组下标越界的情况。
②若编译器不支持bool类型,则可用int代替,只不过占用内存变多
③为什么j是long long类型呢?因为i*i的过程中可能会出现“溢出”的情况,若溢出则j会变成负数,所以我么要避免这种情况发生,所以要类型强转
运行结果
③欧拉筛法
int countPrimes(int n) { bool flag[n+1]; int cnt = 0; int prime[n+1]; memset(flag, 0, sizeof(flag)); for (int i = 2; i < n; i++) { if (!flag[i]) { prime[++cnt] = i;//prime数组记录素数 } for (int j = 1; i * prime[j] < n; j++)//筛去合数 { flag[i * prime[j]] = 1; if (i % prime[j] == 0)//精髓 break; } } return cnt; }1.核心算法
①首先我们要创建两个数组。用flag数组标记每个对应下标的数是否为质数,为0则为质数,为1则为合数。用prime数组记录遍历过程中找到的质数
②在筛去合数过程中,用j来遍历prime数组。此时prime数组里的质数相当于A=B*C模型中的最小因数B,而i则相当于是C。不过每次循环是先确定C,再由B的改变来筛去A的
③为什么 i % prime[j] == 0 时要break?原因是为了避免重复筛,这是这段代码的精髓。怎么理解呢?上面我们也提到B需要是最小因数,以这个逻辑我们可以筛掉所有的数而不重复。如果 i % prime[j] == 0,则不能保证prime[j]是此时的最小因数,还可能是 i / prime[j] ,如i==6,prime[j]==3的时候。那怎么直观点的理解呢? i % prime[j] != 0则说明了二者之间的“唯一性”,那么这样进行遍历时就不会出现重复。那为什么要break呢?如果此时继续运算,那么 i*prime[j+1]=prime[j]*k*prime[j+1],那么显然prime[j+1]不会是最小质数。
2.本段代码注意点
①flag和prime数组都是通过变长数组的方式实现的,他是C99才有的标准,在你的编译器下可能并不支持,这样的话也不妨开辟一个很大的数组
②flag和prime数组长度为n+1,是为了防止n=0的时候创建出长度为0的数组
③内部for循环不用担心j会大于cnt,反过来想,即使大于,由于此时prime[j]未被初始化为负值,循环自然终止
运行结果 :
看来这道题目不适合用欧拉筛法藍



