【Leetcode】204. Count Primes

题目地址:

https://leetcode.com/problems/count-primes/

给定 n n n非负,问小于 n n n的素数个数。

法1:埃拉托斯特尼筛法(Sieve of Eratosthenes)。开一个长度为 n n n的boolean数组记录每个数是否是素数。首先全填充为true,将 0 0 0 1 1 1置为false。接着开始找第一个是true的数,是 2 2 2,接着把 2 2 2的比自己大的倍数全标记为false;接着再向后找是true的数,然后重复上面的操作。这样小于 n n n的所有非素数就全被标记为false了,剩下的就是素数。最后数一下即可。代码如下:

import java.util.Arrays;

public class Solution {
    public int countPrimes(int n) {
        if (n <= 2) {
            return 0;
        }
        
        boolean[] isPrime = new boolean[n];
        Arrays.fill(isPrime, true);
        isPrime[0] = isPrime[1] = false;
        
        for (int i = 2; i <= Math.sqrt(n - 1); i++) {
        	// 遇到素数就筛掉它的比它大的倍数
            if (isPrime[i]) {
                for (int j = 2; i * j < n; j++) {
                    isPrime[i * j] = false;
                }
            }
        }
        
        // 最后数一下还有哪些数没被筛掉
        int res = 0;
        for (boolean p : isPrime) {
            if (p) {
                res++;
            }
        }
        
        return res;
    }
}

时间复杂度 O ( n log ⁡ log ⁡ n ) O(n\log \log n) O(nloglogn),空间 O ( n ) O(n) O(n)

时间复杂度证明依赖于一个式子: ∑ p < n 1 p = log ⁡ log ⁡ n \sum_{p<n}\frac{1}{p}=\log\log n p<np1=loglogn有了这个式子,时间复杂度是显然的。

法2:欧拉筛。这种筛法时间复杂度是线性的,优于埃拉托斯特尼筛法。它的主要改进在于,让每个合数被筛的时候,只被它的最小素因子筛。这样就能保证每个合数只会被筛一次,而不像埃氏筛法,同一个数可能被多次筛。代码如下:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Solution {
    public int countPrimes(int n) {
        boolean[] isPrime = new boolean[n + 1];
        Arrays.fill(isPrime, true);
        // 要把素数存下来
        List<Integer> primes = new ArrayList<>();
    
        for (int i = 2; i < n; i++) {
            if (isPrime[i]) {
                primes.add(i);
            }
            
            for (int j = 0; i * primes.get(j) < n; j++) {
                isPrime[i * primes.get(j)] = false;
                if (i % primes.get(j) == 0) {
                    break;
                }
            }
        }
        
        return primes.size();
    }
}

时空复杂度 O ( n ) O(n) O(n)

算法正确性证明:
假设有个合数 N N N,其最小素因子是 p p p,那么由于 p p p是素数,所以当外层循环的 i i i p p p的时候,isPrime[i * primes.get(j)] = false;这句话在之前的所有循环中都没把 p p p给筛掉,所以一进循环的时候 p p p就被加入了primes的列表中。接下来循环进行到 i = N p ≥ p i=\frac{N}{p}\ge p i=pNp的时候,由于 i i i的最小素因子不会小于 p p p,所以不会提前break出来,因此 N N N会被筛掉。

算法时间复杂度证明:
只需证明每个合数 N N N只会被筛一遍,也就是 i = N p i=\frac{N}{p} i=pN的那一遍。首先,在isPrime[i * primes.get(j)] = false;的时候, p j p_j pj一定小于等于 i i i的所有素因子,否则就会被提前break出去。而这种情况下, p j p_j pj也是 i p j ip_j ipj的最小素因子,也就是说,当 N N N被筛的时候,一定有 p j = p p_j=p pj=p,所以也一定有 i = N p i=\frac{N}{p} i=pN,而 i i i N p \frac{N}{p} pN并且 p j p_j pj p p p两者一起只会发生一次,所以 N N N也只会被筛一次。

C++:

class Solution {
 public:
  int countPrimes(int n) {
    if (!n) return 0;
    bool st[n];
    memset(st, 0, sizeof st);
    int p[n], idx = 0;
    for (int i = 2; i < n; i++) {
      if (!st[i]) p[idx++] = i;
      for (int j = 0; p[j] * i < n; j++) {
        st[p[j] * i] = true;
        if (i % p[j] == 0) break;
      }
    }

    return idx;
  }
};

时空复杂度 O ( n ) O(n) O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值