【LeetCode】第204题——计数质数(难度:简单)

这篇博客介绍了LeetCode第204题——计数质数的解决方案,包括暴力法、埃氏筛和线性筛三种思路,并详细解释了每种方法的逻辑,同时提供了代码实现。文章还提醒在使用平方根优化时要注意类型转换,防止整数溢出。
摘要由CSDN通过智能技术生成

【LeetCode】第204题——计数质数(难度:简单)

题目描述

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

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

  2. 示例 2:
    输入:n = 0
    输出:0

  3. 示例 3:
    输入:n = 1
    输出:0

提示:

0 <= n <= 5 * 106

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-primes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

思路一:暴力法

很容易想到的一个方法,遍历到 i 时,就看看**2~sqrt(i)**间有无数字可以被 i 整除,有则是合数,无则为质数。

思路二:埃氏(Eratosthenes)筛
遍历到质数 i 时,把 i×2、i×3…直至 i×j<n标记为合数。
初始全标记为质数,然后如一开始遍历到2时,2是质数标记,质数计数器+1,并把4、6、8…标记为合数,遍历到3时,3是质数标记,质数计数器+1,并把6、9…标记为合数,遍历到4时,4是合数标记,计数器不变,并把8、12…标记为合数,以此类推。
当然也不用遇到每个质数 i 都从2倍的、3倍的开始标记,可以从 i倍的 ii+1倍的 i开始遍历。

思路三:线性筛
也不能说是思路二的优化,线性筛与埃氏筛相比,是拿空间换时间。
如果理解了思路二,会发现在遍历到3时,会把12标记一遍,在遍历到4时,也会把12标记一遍,即重复标记。线性筛便是通过开辟一额外数组以记录遍历过的质数,来防止重复遍历。

防止重复遍历这一概念比较难以理解,下面提出一种自认为较明了的理解方式。(斜体数字为遍历的当前数,加粗数字为数组中提取的数)
遍历到2–>2为质数标记–>把2放入数组中–>[2]–>2×2=4–>把4记录为合数
遍历到3–>3为质数标记–>把3放入数组中–>[2,3]–>3×2=6,3×3=9–>把6、9记录为合数
遍历到4–>4为质数标记–>不把4放入数组中–>[2,3]–>4×2=8,但是4%2=0,这里不把后续的12标记为合数–>只把8记录为合数

为什么遍历至能被整除便停止标记?
假设遍历至数字a,且a为质数标记,数组中有数字b1–>把a放入数组中,把a×b1标记为合数–>若a%b1=0(a/b1=x),那么对于数组中的下一个数字b2而言,a×b2这一个数会在遍历至a/b1×b2=x×b2时又被标记一次,因此不把a×b2这一个数在当前循环中标记为合数,而在遍历至x×b2时把a×b2这个数标记为合数。

代码详解

埃氏筛

class Solution {
    public int countPrimes(int n) {
        int[] nums = new int[n];	// 0与1的数组,0记为质数,1记为合数
        int count = 0;	// 质数计数器
        for(int i = 2; i < n; ++i) {	// 从2开始遍历
            if(nums[i] == 0) {
                ++count;	// 如果标记为质数则计数器+1
                if((long)i * i < n) {	// 注意要用long强转,不然越界
                    for(int j = i * i; j < n; j = j+i) {	// 把i倍的i、i+1倍的i...标记为合数
                        nums[j] = 1;
                    }
                }
            }
        }
        return count;
    }
}

线性筛

class Solution {
    public int countPrimes(int n) {
        int[] nums = new int[n];	// 0与1的数组,0记为质数,1记为合数
        int count = 0;	// 质数计数器
        ArrayList<Integer> array = new ArrayList<>();	// 新开辟的数组,用于存放以遍历过的质数
        for(int i = 2; i < n; ++i) {
            if(nums[i] == 0) {
                ++count;		// 标记为质数时计数器+1
                array.add(i);	// 且把该质数放入数组array中
            }
            for(int j = 0; j < array.size() && array.get(j) * i < n; ++j) {
                nums[array.get(j) * i] = 1;
                if(i % array.get(j) == 0) {	// 一旦能被整除便停止内层循环
                    break;
                }
            }
        }
        return count;
    }
}

注意点

  1. sqrt(a)比a×a计算速度慢,但在计算a×a时注意用long强转,防止越界。
  2. 线性筛与埃氏筛相比,用空间换时间,且该方法不好想到,因此本人更推荐埃氏筛,线性筛作为扩展。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值