LeetCode精讲(0204):计算小于n的质数数量(Python)

本文探讨了统计小于非负整数n的质数数量的算法,从完全暴力算法逐步优化至高效的“厄拉多塞筛法”,并最终实现Python代码的极致优化。
部署运行你感兴趣的模型镜像

题目内容

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

示例:

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

题目标签:数学、哈希表

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

运行效率

解法时间复杂度空间复杂度执行用时
Ans 1 (Python)O(n2)O(n^2)O(n2)O(1)O(1)O(1)超出时间限制
Ans 2 (Python)O(nn)O(n\sqrt{n})O(nn)O(1)O(1)O(1)超出时间限制
Ans 3 (Python)O(nn)O(n\sqrt{n})O(nn)O(1)O(1)O(1)超出时间限制
Ans 4 (Python)O(n2)O(n^2)O(n2)O(n)O(n)O(n)752ms (>37.08%)
Ans 5 (Python)O(nn)O(n\sqrt{n})O(nn)O(n)O(n)O(n)476ms (>53.63%)
Ans 6 (Python)O(nn)O(n\sqrt{n})O(nn)O(n)O(n)O(n)124ms (>89.48%)

解法一(完全暴力算法):

【思路】

首先,依据质数的定义,我们得到如下算法:

遍历所有小于目标值(n)且大于等于2的整数©;并将这些整数©除以小于它们©且大于等于2的整数(i)来判断它们是不是质数。

这样的方法显示十分麻烦,每个数©都要除以c-1个数才能判断完成,此时的时间复杂度为O(n^2)。

def countPrimes(self, n: int) -> int:
    def is_primes(c):
        """
        判断c是否为质数
        """
        for i in range(2, n):
            if c % i == 0:
                return False
        return True

    ans = 0
    for j in range(2, n):
        if is_primes(j):
            ans += 1
    return ans

解法二(不那么暴力的暴力算法):

【思路】

实际上,并不需要除以所有小于等于某整数©的整数来判断该整数©是否为质数。

因为,除该整数©的平方根外,其他所有可以整除该整数的两个数,都是一个小于平方根,一个大于平方根。例如:4 < sqrt(24) < 6

所以,当除到该整数©的平方根时,就已经可以将所有可能的情况枚举出来了。

由此,我们对算法优化如下:

def countPrimes(self, n: int) -> int:
    def is_primes(c):
        """
        判断c是否为质数
        """
        for i in range(2, int(pow(c, 0.5)) + 1):
            if c % i == 0:
                return False
        return True

    ans = 0
    for j in range(2, n):
        if is_primes(j):
            ans += 1
    return ans

解法三(更不暴力的暴力算法):

【思路】

在解法二的基础上,我们发现其实如果已经除了2,那么再除4、6等偶数都是没有意义的。

因此,我们将算法优化为只除以之前已经发现的质数,得到如下算法:

def countPrimes(self, n: int) -> int:
    prime_list = []
    for c in range(2, n):
        for i in prime_list:
            if c % i == 0:
                break
        else:
            prime_list.append(c)
    return len(prime_list)

解法四(使用数组标记合数):

【思路】

既然后面的数我们已经是通过之前除以之前发现的质数来判断是否为质数,那没有其实已经没有必要再继续使用耗时更高除法了。

我们可以建立一个数组,用以标记各个数是否为质数;当我们每发现一个质数,就遍历将所有该质数的倍数标记为合数;这种方法叫做“厄拉多塞筛法”。

由此,我们得到如下算法,虽然时间复杂度为O(n^2),但是不用再使用除法了:

def countPrimes(self, n: int) -> int:
    num_list = [True for _ in range(n)]

    for i in range(2, n):
        if num_list[i]:  # 如果i为质数(不是任何质数的倍数)
            for j in range(2 * i, n, i):
                num_list[j] = False

    ans = 0
    for i in range(2, n):
        if num_list[i]:
            ans += 1
    return ans

解法五(优化解法四):

【思路】

因为我们在标记7的倍数时,14、21、28、35、42都已经被2、3、5标记过了,即7*7以下的7的倍数都已经被标记了,所以,我们只需要从7*7向上标记7的倍数就可以了。

因此,当我们判断到n平方根(即标记完小于等于n平方根的最大质数)后,就不用再判断和标记了。

由此,我们得到了如下算法:

def countPrimes(self, n: int) -> int:
    num_list = [True for _ in range(n)]

    for i in range(2, int(pow(n, 0.5)) + 1):
        if num_list[i]:  # 如果i为质数(不是任何质数的倍数)
            for j in range(i * i, n, i):
                num_list[j] = False

    ans = 0
    for i in range(2, n):
        if num_list[i]:
            ans += 1
    return ans

解法六(优化解法五):

【思路】

因为在Python中,Python的内置语句往往会比我们自己写的算法要更快,因此我们更多地使用一些Python的内置语句来优化算法。

同时,在生成各元素相同的列表时,“列表生成式”的运行速度比“列表乘以常数”的运行速度要慢很多,不再使用列表生成式。

def countPrimes(self, n: int) -> int:
    if n < 2:
        return 0

    num_list = [True]*n
    num_list[0], num_list[1] = False, False

    for i in range(2, int(pow(n, 0.5)) + 1):
        if num_list[i]:  # 如果i为质数(不是任何质数的倍数)
            num_list[i * i::i] = [False] * ((n - i * i - 1) // i + 1)  # 因为要包含i*i所以需要+1;因为n不在列表里,所以需要-1

    return sum(num_list)  # True就是1,False就是0,可以直接统计

至此,其实我们的算法已经优化得很好了,把那些枚举LeetCode测试数据的人忽略掉就行了。

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值