最近看到挺有意思的一题,原题链接:https://app.codility.com/programmers/lessons/11-sieve_of_eratosthenes/count_semiprimes/
题目描述
质数是一个正整数X,它有两个不同的除数:1和X。最初的几个质数是2、3、5、7、11和13。
半质数(semiprime)是一个自然数,它是两个质数的乘积(不一定是不同的)。前几个半素数是4,6,9,10,14,15,21,22,25,26。
给定两个非空数组 P 和 Q,每个数组由 M 个整数组成。这些数组表示有关指定范围内半素数个数的查询,返回 [ P[k], Q[k] ] 左闭右闭区间半质数
的个数,其中1≤ P [ K ] ≤ Q [ K ]≤ N。
例如:P = [1, 4, 16], Q = [26, 10, 20]
, 返回 [10, 4, 0]
。
P[0], Q[0] --> [1, 26] 中间半质数有4,6,9,10,14,15,21,22,25,26, 共 10 个 P[1], Q[1] --> [4, 10] 中间半质数有4,6,9,10, 共 4 个 P[2], Q[2] --> [16, 20] 中间半质数共 0 个
思路与实现
由于1 ≤ P[K] ≤ Q[K] ≤ N
,所以先存 0 到 N 每个数到当前数的半质数个数: dict = {数:到当前数的半质数个数}
,这样对于每个 P[k] 到 Q[k] 区间,只需要计算dict[Q[k]] - dict[P[k]]
,当 P[k] 为半质数时需要 +1。
如何求 0 到 N 每个数到当前数 x 的半质数个数,可以简单描述为 dict[x] = dict[x-1]+1 if x是半质数 else dict[x-1]
。
然后问题来到如何求一个数 x 是否是半质数:将 x 拆成两个数的乘积,再判断这两个数是否都为质数。又因为将 x 拆成两个数的乘积,一定有一个数小于根号 x,所以只需要遍历到根号 x 来降低复杂度。
def solution(N, P, Q): res = [] # 寻找N内半质数 semiprime_dict = {} prime_nums = set() # 存质数,不用重复判断,节省空间也可以不要 for i in range(2, int(N**0.5)+1): # 不是质数 if i not in prime_nums and not is_prime(i): continue prime_nums.add(i) for j in range(i, int(N/i)+1): if j not in prime_nums and not is_prime(j): continue # 如果i, j 都是质数 prime_nums.add(j) # 半质数加入semiprime_dict中 semiprime_dict[i*j] = 0 # semiprime_dict={数:到当前数的半质数个数} 当前只有半质数,将 semiprime_dict 扩展到所有数 # (实现的有点复杂 semiprimes = sorted(semiprime_dict) # 排序后的键值 count = 0 for x in semiprimes: # 对于每个半质数,到当前的半质数个数 count += 1 semiprime_dict[x] = count semiprime_dict[0] = 0 # 1,2,3都应该是0,加入0是解决P[i]=1的情况 for x in range(1, N+1): # 非半质数,到当前的半质数个数与前一个相同 if x not in semiprime_dict: semiprime_dict[x] = semiprime_dict[x-1] # 找 P[i]-Q[i]之间semiprime_list的元素个数 O(M) for i in range(len(P)): count = semiprime_dict[Q[i]] - semiprime_dict[P[i]] if semiprime_dict[P[i]] > semiprime_dict[P[i]-1]: count += 1 res.append(count) return res # 判断质数 def is_prime(x): # 偶数一定不是质数 if x != 2 and x % 2 == 0: return 0 # x 拆成两个数的乘积一定有一个小于根号x for i in range(2, int(x**0.5)+1): if x % i == 0: return 0 return 1