[数学规律] 204. 计数质数 (质数判断方法:暴力解 → 缩小范围 → 引入bitmap)

204. 计数质数

题目链接:https://leetcode-cn.com/problems/count-primes/

分类

  • 数学规律(找出质数的判断方法)
  • 算法优化(暴力解 → 缩小范围 → 引入bitmap)
  • 哈希表(bitmap的思想实质上是哈希表)**

在这里插入图片描述

题目分析

质数就是一个数只能被1和它自己整除,注意1不是质数。下面会列举几种计算质数的方法,方法是不断优化的。

  • 思路1采用的是根据质数的判定方法,枚举每个数字,判断是不是质数,在此基础上做优化;
  • 思路2采用的是设置一个bitmap数组标记非质数,把质数的所有倍数下的数字全部标记为非质数,最后统计剩余的数字即为质数。

题目难点

  1. 以暴力解为基础不断优化:找出质数,缩小范围;
  2. 跳脱暴力解的思想,从另一个角度统计质数:bitmap,找出所有非质数;

思路1:暴力解

统计[2,…,n-1]中质数的数量。

如何判断一个数是不是质数?

对于区间内的每个数i,都依次计算2~i-1是否存在能够整除i的数:如果存在,说明它不是质数,如果都不能整除i,则说明i是质数。

实现代码

class Solution {
    public int countPrimes(int n) {
        int res = 0;
        //计算2~n-1中有多少质数
        for(int i = 2; i < n; i++){
            //如果i是质数,则res+1
            if(isPrime(i)) res++;
        }
        return res;
    }
    //判断一个数是否为质数
    public boolean isPrime(int n){
        //判断2~n-1区间内是否有可以整除n的数
        for(int i = 2; i < n; i++){
            //只要有一个数,就说明n不是质数,返回false
            if(n % i == 0) return false;
        }
        return true;
    }
}
  • 时间复杂度:区间内共n-1-2+1=n-2个数,每个数最差情况下需要计算i-1次,所以最差时间复杂度为O(N^2)

  • 存在的问题:最简单的算法,但是效率低,超时。

思路1的优化:缩小计算范围

思路1中判断一个数i是不是质数,需要计算2~i-1是否存在能够整除i的数,实际上并不需要计算那么多数,只需要计算到i/2即可,> i/2的数不可能能够整除i。

实现代码
把原来isPrime里的for循环条件:

for(int i = 2; i < n; i++)

改为:

for(int i = 2; i <= n / 2; i++)

但时间复杂度仍为:O(1/2*N^2)=O(N^2)

还可以进一步缩小范围
例如:判断12是不是质数,则会经历如下的过程:

12 = 2 * 6
12 = 3 * 4
12 = √12 * √12
12 = 4 * 3
12 = 6 * 2
...
(按前面isPrime的设计,非质数12在得到12%2==0后就会返回false,但如果是质数,则会计算下面这些所有的步骤,带来计算量的冗余,
  这里举例的目的是为了找出计算的冗余在哪,以非质数为例只是为了让因子更好写)

可以发现以√12为分界线,后面做的操作实际上在前面已经做过了,所以for循环实际上只需要枚举到 i == √n,即 i * i <= n。

实现代码

class Solution {
    public int countPrimes(int n) {
        int res = 0;
        //计算2~n-1中有多少质数
        for(int i = 2; i < n; i++){
            //如果i是质数,则res+1
            if(isPrime(i)) res++;
        }
        return res;
    }
    //判断一个数是否为质数
    public boolean isPrime(int n){
        //判断2~n-1区间内是否有可以整除n的数
        for(int i = 2; i * i <= n; i++){
            //只要有一个数,就说明n不是质数,返回false
            if(n % i == 0) return false;
        }
        return true;
    }
}
  • 时间复杂度降为:O(N * √N),可以AC,但还有优化的空间。

思路2:引入bitmap

bitmap实际上就是设置一个类型为boolean,大小为n的数组,bitmap[i]表示数字 i 是不是质数,false表示不是质数,true表示是质数,初始时全部置为true。(这样设置是为了填充bitmap时方便处理)。

bitmap保存的就是[2,…,n-1]中每个数是不是质数,在填充完bitmap后,遍历一次bitmap,统计元素值为 true 的元素的数量,即为所有小于非负整数 n 的质数的数量。

如何填充bitmap?

因为bitmap初始时全部填充为true,所以bitmap[2] == true,可以作为第一个起点开始填充。

如果元素值bitmap[i]为true,说明它是质数,就将bitmap[i2],bitmap[i3],bitmap[i*4],…,全部置为false,表示这些数都不是质数,直到下标超过n时为止;

再选择bitmap[i+1]为true的元素为起点,将起点下标不断乘以2,且下标对应的元素值置false,直到下标超过n为止;

以此类推,当i == n时,说明整个数组都填充完毕,最后统计数组中置为true的元素数量即可。

思路2的优化

和思路1的优化一样,外层for-i循环并不需要遍历2~n-1所有数字,只需要枚举到√n即可,用代码可以表示为:

修改前的外层for-i循环:

for(int i = 2; i < n; i++)

修改后:

for(int i = 2; i * i <= n; i++)
//用i * i 来代替使用Math.sqrt。

实现代码:思路2优化后的代码

class Solution {
    public int countPrimes(int n) {
        boolean[] bitmap = new boolean[n];//bitmap[i]表示数字i是不是质数
        //初始时填充true,为bitmap[2]作为第一个起点做准备
        Arrays.fill(bitmap, true);

        int res = 0;
        //计算2~n-1中有多少质数
        for(int i = 2; i * i <= n; i++){//填充bitmap的起点
            //如果当前数字i是质数,则以i为起点不断乘以2,3,4,...,元素值全部置false
            if(bitmap[i]){
                for(int j = i * 2; j < n; j += i){//j+=i等价为i不断乘以2,3,4,...
                    bitmap[j] = false;
                }
            }
        }
        //统计bitmap中记录的质数个数
        for(int i = 2; i < n; i++){
            if(bitmap[i]) res++;
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值