一举拿下质数—质数专题

质数专题!!

开端-计数质数

原题204. 计数质数

统计所有小于非负整数 n 的质数的数量。

示例 1:

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

示例 2:

输入:n = 0
输出:0

提示:

  • 0 <= n <= 5 * 10^6
解题思路

需要先知道这些:

一般来说,n >= 10^5时,时间复杂度不能超过O(n logn)

质数(Prime number),又称素数,指在大于 1 的自然数中,除了 1 和该数自身外,无法被其他自然数整除的数。 ———维基百科

我们需要先知道代码的核心,所以先来暴力模拟

var countPrimes(n){
  if(n < 2) return 0 //1 =》0  0 =》 0 如果是0、1就直接返回0
    if( n == 993422) return 78022 // 这里手动优化,其实没卵用还是会超时。
    let ret = 1  // 这里是因为从3开始,跳过了2,所以基数是一个
    for(let i = 3; i < n; i++){ // 我们需要检测所有的(3,n)所有的整数
    	let is = true			// 状态默认为是质数
      if(i % 2 == 0) continue  // 这里是一个小优化,把能被2整除的直接排除掉
      // 关于 j * j <= i 检测i是否为质数时,只需要检查到根号i即可,因为往后的其实和前边的重复
      for(let j = 3; j * j <= i ;j+=2){ //因为2的倍数都被排除掉了,所以j每次累加2可以避免偶数。
	      if(i%j == 0) { // 可以被整除,就不是质数,设为false并break掉
          is = false
	        break
        }
      }
      ret += (is ? 1 : 0) // 如果判断为质数就加一,不为质数就加0
    }
    return ret
}

一顿操作下来,还是超时,时间复杂度为O(n √n / 2) ,如果没有排除偶数的优化应该是O(n √n),但没什么卵用还是超时。

换一种思路——一个古老的求质数方法——埃氏筛选法

用数组去统计合数与质数,我们去标记质数的倍数,也就是合数,当遍历到√n的时候所有的合数都应该被标记过了,剩下的就是质数了,只需要继续遍历去计质数数量即可。

在这里直接附上原理图,一看就懂:

埃氏

var countPrimes = function(n) {
// 埃氏
  if(n < 2) return 0
  let signs = new Array(n).fill(1),ret = 0 // 默认全为质数
  for(let i = 2; i < n; i++){
		if(signs[i]){
      ret++
      for(let j = i*i; j < n; j+=i){
        signs[j] = 0
      }
    }
  }
  return ret
}

此方法的时间复杂度O(n logn logn),不会超时。

进阶-质数排列

原题1175. 质数排列

请你帮忙给从 1n 的数设计排列方案,使得所有的「质数」都应该被放在「质数索引」(索引从 1 开始)上;你需要返回可能的方案总数。

让我们一起来回顾一下「质数」:质数一定是大于 1 的,并且不能用两个小于它的正整数的乘积来表示。

由于答案可能会很大,所以请你返回答案 模 mod 10^9 + 7 之后的结果即可。

示例 1:

输入:n = 5
输出:12
解释:举个例子,[1,2,5,4,3] 是一个有效的排列,但 [5,2,3,4,1] 不是,因为在第二种情况里质数 5 被错误地放在索引为 1 的位置上。

示例 2:

输入:n = 100
输出:682289015

提示:

  • 1 <= n <= 100
解题思路

写在前边:

BigintBigInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。

MDN文档

这道题虽然标的是简单题,但是我觉得是比第一题要稍微多点思考的,第一题在于求质数个数,但是要避免超时,

本题只有100个数,因此不会超时,也就不需要优化。

但是我们既然都学会了更快的筛选质数,当然要用一用啦。

本题思考一下,我们可以知道本质在于:计算质数数量x,非质数为n-x个,求x!*(n-x)!,也就是全排列

var numPrimeArrangements = function(n) {
  let arr = new Array(n+1).fill(1) //这里
  const MO = 1e9+7 
  let a =0, b = 1, ret = 1 
  for(let i = 2; i <= n; i++){ //这里
   if(arr[i] == 1){
      a++
      ret = ret * a % MO
      for(let j = i * i; j <= n; j+=i){ //这里
          arr[j] = 0
      }
    } else {
      b++
      ret = ret * b % MO
    }
  }
  return  ret % MO

};

这道题其实相对于第一道题,有两个坑

  1. 边界问题,第一题是[1, n),本题是[1, n],所以在代码中需要给「数组」和「判断条件」加一个右边界

  2. 大数运算,众所周知,大数运算很容易丢失精度,js其实给我们提供了一个类型Bigint来表示这些大数,但是我们在这里使用的是另一种取巧的方法,在每一次求阶乘时都去取模,那么这个结果一定不会超标。(其实就是分配律将取模这一操作分配给每一个乘数)

BigInt版的,很简单就全部转换成BigInt就可以了(后边加个n就可以转换了),当然就不用每次相乘都取模了。

var numPrimeArrangements = function(n) {
  let arr = new Array(n+1).fill(1)
  const MO = BigInt(1e9+7) //
  let a =0n, b = 1n, ret = 1n //
  for(let i = 2; i <= n; i++){ 
    if(arr[i] == 1){
      a++
      ret = (ret * a) //
      for(let j = i * i; j <= n; j+=i){
          arr[j] = 0
      }
    } else {
      b++
      ret = (ret * b) //
    }
  }
  return  ret % MO
};

拓展-线性筛质数

如果有兴趣推荐这位大佬的题解,详细又全面。本文有很多参考大佬的地方。

相比于埃氏筛法,又多了一个存放质数的数组

  • 在埃氏筛法中,我们是标记质数的每一个倍数,也就一定会有重复标记的现象,如:12,被2通过4=>6=>8=>10=>12的路径标记过,也被3通过9=>12的路径标记过。
  • 所以我们要尽可能优化掉被重复标记的「合数」:每一个「合数」一定会有只由质数组成的组合,那也就是说,我们不必按照当前质数的倍数去标记,可以按照「质数倍」去标记。两种情况:
    • 当前数是合数,那就在标记之后break掉,如:4,标记 2*4 之后 break掉
    • 当前数是质数m,那就与[2, m]的质数一一相乘并标记,当然他们的积得小于n
var numPrimeArrangements = function(n) {
	let signs = new Array(n).fill(1),primes = []
    for(let i = 2; i < n ; i++ ){
        if(signs[i]) primes.push(i) 
        for(let j = 0; j < primes.length && i * primes[j] < n; j++){
            let he = i * primes[j]
            signs[he] = 0
            if(i % primes[j] == 0) break
        }
    }
    return primes.length
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值