菜鸟前端刷算法第十二天

152 阅读3分钟

题目描述 - 计数质数

给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。

质数的定义:在大于 1 的自然数中,除了1和它本身以外不再有其他因数的自然数。即,除了 1 和 它本身不能被任何数整除。

示例:

  • 输入: 输入 n = 10,输出: 4,解释:小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

思路分析

遍历枚举

对于小于 n 的区间 【2, n - 1】 内每一个数 n 进行判断,是否存在能被 n 整数的数。如果存在,则 n 不是质数。如果不存在,则 n 是质数。

在遍历的同时,我们维护一个用于统计小于 n 的质数数量的变量 count 。如果符合要求,则将计数 count 加 1 ,最终返回该数目作为答案。

代码实现:

/**
 * @param {number} n
 * @return {number}
 */
var countPrimes = function(n) {
    let count = 0;
    function isPrime(num){
        if(num == 0) return false
        if(num == 1 || num == 2) return true
        let flag = true
        for(let i=2; i<num; i++){
            // num % i===0 则 存在整除 非质数 等价于 !(num % i)
            if(!(num % i)) {
                flag = false; 
                break ;
            }
        }
        return flag
    }
    for(let i=2; i<n; i++){
        if(isPrime(i)) count++
    }
    return count
};

上述代码虽然实现了质数的计数,但是在 leetCode 运行之后,会提示超时,因此我们需要进行优化。我们分析 4~8 的因式分解过程如下:

  • 4: 1 * 4    sqrt(4) * sqrt(4) = 2 * 2    4 * 1
  • 5: 1 * 5    sqrt(5) * sqrt(5)    5 * 1
  • 6: 1 * 6    2 * 3    sqrt(6) * sqrt(6)    3 * 2    2 * 1
  • 7:1 * 7    sqrt(7) * sqrt(7)    7 * 1
  • 8:1 * 8    2 * 4    sqrt(8) * sqrt(8)    4 * 2    8 * 1

合数质数sqrt前后因式一样。以sqrt为界,只要能被前面数整除,该数不是质数。优化代码如下:

/**
 * @param {number} n
 * @return {number}
 */
var countPrimes = function(n) {
    let count = 0;
    function isZhi(num){
        if(num == 0) return false
        if(num == 1 || num == 2) return true
        let flag = true
        for(let i=2; i<=Math.sqrt(num); i++){
            if(!(num % i)) {
                flag = false; 
                break ;
            }
        }
        return flag
    }
    for(let i=2; i<n; i++){
        if(isZhi(i)) count++
    }
    return count
};
埃氏筛法

这种方法是由古希腊数学家埃拉托斯尼斯提出的,其基本原理为:质数 的倍数是 合数 。那么 n 之内,从 2 开始,找到质数则计数加 1, 且顺序标记质数的倍数为合数

质数.png

实现步骤如下:

  • 使用长度为 n 的数组 isPrime 来判断一个数是否是质数。如果 isPrime[i] == True ,则表示 i 是质数,如果 isPrime[i] == False,则表示 i 不是质数。使用变量 count 标记质数个数。
  • 从 【2, n - 1】 的第一个质数(数字 2) 开始,count 加 1,并将该质数在 [2, n - 1] 范围内所有倍数(即 4、6、8、…)都标记为非质数。
  • 然后根据数组 isPrime 中的信息,找到下一个没有标记为非质数的质数(即数字 3),count 加 1,然后将该质数在 【2, n - 1】 范围内的所有倍数(即 6、9、12、…)都标记为非质数。
  • 以此类推,直到所有小于或等于 n - 1 的质数和质数的倍数都标记完毕时,输出 count。

代码实现:

/**
 * @param {number} n
 * @return {number}
 */
var countPrimes = function(n) {
    let count = 0;
    let isPrime = Array(n).fill(true);
    for(let i = 2; i < n; i++) 
        if(isPrime[i]) {
            count++
            // 标记 i 的倍数为合数
            for(var j = 2 * i; j < n; j += i) isPrime[j] = false
        }
    return count
};