LeetCode精选TOP面试题204.计数质数

本文详细介绍了四种解决小于非负整数n质数数量问题的高效算法:枚举法、埃氏筛法、线性筛法和减法法。通过对比分析,展示了思路4(减法法)的时间复杂度为O(n),空间复杂度也为O(n),在LeetCode平台上表现最佳。
摘要由CSDN通过智能技术生成

题目描述

  • 给定整数 n ,返回 所有小于非负整数 n 的质数的数量 。
example
input  : n = 10
output : 4
input  : n = 499979
output : 41537
input  : n = 1
output : 0

解题思路

  • 思路4在leetcode平台提交性能最好:

66 / 66 个通过测试用例
状态:通过
执行用时: 40 ms
内存消耗: 45.6 MB

思路1 枚举

  • 遍历 [2, n) 的所有整数,逐个判断是否为质数,若是则计数结果累加1
  • 时间复杂度:O(n √n})
  • 空间复杂度:O(1)

思路2 埃氏筛

  • 当前元素 x 如果是质数,则其倍数 2x, 3x, …… 一定是合数。
  • 每次遍历到一个质数,就可以筛掉众多是其倍数的合数。
  • 此方法存在的一个问题是重复筛选,如 15 是 3和5 的倍数,15会被筛两次,虽然不影响结果,但实际上只筛1次就足够了。
  • 时间复杂度:O(n log(log n))
  • 空间复杂度:O(n)

思路3 线性筛

  • 让每个合数只被标记一次,相较于埃氏筛,多维护一个 primes 数组表示当前得到的质数集合。
  • 从小到大遍历,如果当前的数 x 是质数,就将其加入 primes 数组。
  • 详见官方题解给出的思路

思路4 减法

  • 首先,除2以外的所有偶数都不是质数,因此在 [1, n] 范围内有最多 n / 2 个质数
  • 其次,筛选所有奇数,把所有是合数的奇数都从 n / 2 这个最大值中减去
  • 最后,剩下的差值就是 [1, n] 内所有的质数了
  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)。

代码(Java)

思路1代码

public class Solution1 {
    public int countPrimes(int n) {
        int result = 0;
        for (int i = 2; i < n; i++) {
            if (judgePrime(i)) {
                result++;
            }
        }
        return result;
    }

    private boolean judgePrime(int number) {
        for (int j = 2; j * j <= number; j++) {
            if (number % j == 0) {
                return false;
            }
        }
        return true;
    }
}

思路2代码

public class Solution2 {
    public int countPrimes(int n) {
        int[] isPrime = new int[n];
        Arrays.fill(isPrime, 1);
        int result = 0;
        for (int i = 2; i < n; i++) {
            if (isPrime[i] == 1) {
                result++;
                if ((long) i * i < n) {
                    for (int k = i; k * i < n; k++) {
                        isPrime[k * i] = 0;
                    }
                }
            }
        }
        return result;
    }
}

思路3代码

public class Solution3 {
    public int countPrimes(int n) {
        List<Integer> primes = new ArrayList<Integer>();
        int[] isPrime = new int[n];
        Arrays.fill(isPrime, 1);
        for (int i = 2; i < n; i++) {
            if (isPrime[i] == 1) {
                primes.add(i);
            }
            for (int k = 0; k < primes.size() && primes.get(k) * i < n; k++) {
                isPrime[primes.get(k) * i] = 0;
                if (i % primes.get(k) == 0) {
                    break;
                }
            }

        }
        return primes.size();
    }
}

思路4代码

public class Solution4 {
    public int countPrimes(int n) {
        if (n <= 2) {
            return 0;
        }
        // 除了2以外,所有偶数肯定不是质数,假设所有奇数全是质数,然后所有奇数中排除非质数,此过程即做减法
        int count = n / 2;

        boolean[] notPrime = new boolean[n];
        // 因为是做减法,且循环内会有对倍数的遍历操作
        // 所以外层循环只需要以 sqrt(n) 为边界
        // 内层遍历会遍历到整个范围内的倍数并作减法
        // 所有 √n 到 n 之间的奇数,若它是 2 到 √n 内某个质数的倍数,则 count 肯定减1
        // 所有 √n 到 n 之间的奇数,若它不是 2 到 √n 内某个质数的倍数,则它肯定是
        for (int i = 3; i * i < n; i += 2) {

            if (notPrime[i]) {
                // 如果是合数,继续遍历
                continue;
            }
            // 此处遍历i的所有i倍及大于i倍的数,并全部标记为合数
            // 因为i的2到i-1倍的倍数在此之前肯定被遍历从2到i-1时标记过了,不需要重复遍历
            // 之所以每次循环加两倍的i,是因为所有的偶数倍数早已经在遍历之前就被默认减去了(n / 2),故不需要再对其做减法。 
            // 举例,i = 5,则以下循环中只需遍历 5*5,7*5, 9*5, 因为 6*5,8*5肯定是偶数,在遍历之前就被默认减掉了。
            for (int k = i * i; k < n; k += 2 * i) {
                if (!notPrime[k]) {
                    // 把质数 i 的 大于等于i倍的倍数都标记为合数,且count对应减去这些倍数的个数
                    notPrime[k] = true;
                    count--;
                }
            }
        }
        return count;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值