所以,当除到该整数(c)的平方根时,就已经可以将所有可能的情况枚举出来了。
由此,我们对算法优化如下:
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 &#61; [True]*n num_list[0], num_list[1] &#61; False, False for i in range(2, int(pow(n, 0.5)) &#43; 1): if num_list[i]: # 如果i为质数(不是任何质数的倍数) num_list[i * i::i] &#61; [False] * ((n - i * i - 1) // i &#43; 1) # 因为要包含i*i所以需要&#43;1&#xff1b;因为n不在列表里&#xff0c;所以需要-1 return sum(num_list) # True就是1&#xff0c;False就是0&#xff0c;可以直接统计
至此&#xff0c;其实我们的算法已经优化得很好了&#xff0c;把那些枚举LeetCode测试数据的人忽略掉就行了。