204 计数质数(2021.06.25)

本文介绍了三种解法来解决计数质数的问题,包括暴力法、埃氏筛和线性筛。详细解释了每种方法的实现思路和时间、空间复杂度,并给出了在LeetCode上的执行结果。线性筛在效率上优于暴力法和埃氏筛。
摘要由CSDN通过智能技术生成

160. 相交链表

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

题目描述见链接内容。

解法1:暴力法

二话不说,用暴力法解决这个问题,遍历n,对所有计数调用isPrime方法,isPrime中要注意的一个点是,结束循环的条件是x * x < n,不是n / 2,遍历到开方之前就可以完成所有的判断了

var countPrimes = function (n) {
  let count = 0;
  for (let i = 2; i < n; i++) {
    if (isPrime(i)) {
      count++;
    }
  }
  return count;
};

function isPrime(n) {
  let x = 2;
  while (x * x <= n) {
    if (n % x === 0) {
      return false;
    }
    x++;
  }
  return true;
}

在计算到5000000用例时超时了

解法2:埃氏筛

这个算法由希腊数学家埃拉多塞提出,成为埃拉多塞筛法,简称埃氏筛

如果x是质数,那么x的倍数2x/3x…一定不是质数,所以声明一个数组isPrimes来表示i是不是质数,0代表不是,1代表是。从小到大遍历x,如果它是合数,那么它一定是某个小于x的质数的y整数倍。

也就是说,在遍历到y时,就会将上面提到的x标记为isPrimes[x] = 0

对于一个质数x,其实不必从2x开始标记,应该直接从x * x开始标记,因为2x3x这些数一定在x之前被其他的数字标记过了,例如标记2的倍数、3的倍数时

var countPrimes = function (n) {
  let result = 0;
  // 初始化,1 代表是质数,0代表不是
  const isPrimes = new Array(n).fill(1);

  for (let i = 2; i < n; i++) {
    if (isPrimes[i]) {
      result += 1;

      // 从 i * i 开始标记,因为2i、3i...一定在 i * i 之前被标记过了,递增策略是 j += i, 这样相当于按照 i 的倍数增加进行标记
      for (let j = i * i; j < n; j += i) {
        isPrimes[j] = 0;
      }
    }
  }

  return result;
};
  • 时间复杂度:${O(N Log(Log(N)))}$
  • 空间复杂度:${O(N)}$
  • 执行用时:232ms, 在所有JavaScript提交中击败了71%的用户,内存消耗:77,2MB,在所有JavaScript提交中击败了46%的用户

解法3:线性筛

相对于埃氏筛,多增加了一个primes数组来存放当前得到的质数集合,从小到达进行遍历,如果当前的数字x是质数,就加入primes数组

另一点与埃氏筛不同的是,标记过程不再当x是质数时进行,而对每个x都进行,不再标记x的倍数1x2x…,而是标记质数集合primes中每个质数与x的乘积,并且在x % primes[i]时结束标记

关键点是:因为如果x可以被primes[i]整除,那么对于下一个要标记的合数x * primes[i + 1],一定会在(x / primes[i]) * primise[i + 1]时被标记。这样就保证了每个合数只会被其最小的质因数筛去,只被标记一次

在对headA的遍历过程中,可以把headA的每一个节点存到一个Set中,然后在遍历headB时,只需要判断当前在节点在之前的Set中是否存在即可

var countPrimes = function (n) {
  // 初始化,1 代表是质数,0代表不是
  const isPrimes = new Array(n).fill(1);
  const primes = [];

  for (let i = 2; i < n; i++) {
    if (isPrimes[i]) {
      primes.push(i);
    }
    // 边界条件有两个
    for (let j = 0; j < primes.length && i * primes[j] < n; j++) {
      isPrimes[i * primes[j]] = 0;

      // 如果 i 可以被 primes[j] 整除,就停止标记,因为下一个 primes[j + 1] 会在 (i / primes[i]) * primes[j + 1] 时被标记
      if (i % primes[j] === 0) {
        break;
      }
    }
  }
  return primes.length;
};
  • 时间复杂度:${O(N)}$
  • 空间复杂度:${O(N)}$
  • 执行用时:224ms, 在所有JavaScript提交中击败了74%的用户,内存消耗:100.6MB,在所有JavaScript提交中击败了12%的用户
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值