|
| 1 | +"""https://app.codility.com/programmers/lessons/11-sieve_of_eratosthenes/count_semiprimes/""" |
| 2 | + |
| 3 | +from math import floor, sqrt |
| 4 | + |
| 5 | +# Time Complexity: |
| 6 | +# Sieve of Eratosthenes to find all primes up to N = O(Nlog(logN)) |
| 7 | +# For each number up to N, check all possible divisors = O(N*sqrt(N)) |
| 8 | +# For M queries, return result in O(M) |
| 9 | +# Overall: O(Nlog(logN) + Nsqrt(N) + M) = O(Nsqrt(N) + M) |
| 10 | + |
| 11 | +def solution_a(n: int, p: list[int], q: list[int]) -> list[int]: |
| 12 | + """ |
| 13 | + P, Q of lengths M represent M queries. |
| 14 | + For each query, find the number of semiprimes within the inclusive range [P[K], Q[K]]. |
| 15 | + A semiprime is the product of two prime numbers. |
| 16 | + E.g. 4, 6, 9, 10, 14 .. are semiprimes |
| 17 | + N is the maximum value in P or Q. |
| 18 | + Compute and return the queries |
| 19 | +
|
| 20 | + This solution passes 100% on Codility tests. The detected TC is O(N * log(log(N)) + M), |
| 21 | + which is not the TC we expected: O(Nsqrt(N) + M) |
| 22 | + """ |
| 23 | + |
| 24 | + # sieve[i] is True if i is prime |
| 25 | + sieve = [True] * (n + 1) |
| 26 | + sieve[0] = sieve[1] = False |
| 27 | + |
| 28 | + # Multiples of the form k * i where k < i are already marked by smaller primes. |
| 29 | + # E.g. for i = 5, multiples 5*2=10, 5*3=15, 5*4=20 are already marked by 2 and 3. |
| 30 | + # Therefore, start from i*i |
| 31 | + |
| 32 | + i = 2 |
| 33 | + while i * i <= n: |
| 34 | + if sieve[i]: |
| 35 | + k = i * i |
| 36 | + while k <= n: |
| 37 | + sieve[k] = False |
| 38 | + k += i |
| 39 | + i += 1 |
| 40 | + |
| 41 | + semiprime_count = 0 |
| 42 | + # prefix[i] is the number of semiprimes up to and including number i |
| 43 | + prefix = [] |
| 44 | + # For each number check all possible divisors |
| 45 | + for num in range(n+1): |
| 46 | + for divisor in range(2, floor(sqrt(num)) + 1): |
| 47 | + # Definition of semiprime: divisor and quotient are prime |
| 48 | + if sieve[divisor] and num % divisor == 0 and sieve[num // divisor]: |
| 49 | + semiprime_count += 1 |
| 50 | + prefix.append(semiprime_count) |
| 51 | + return [prefix[q[i]] - prefix[p[i]-1] for i in range(len(p))] |
| 52 | + |
| 53 | +# Time Complexity: |
| 54 | +# Instantiating SPF (smallest prime factor) sieve over [0..N] = O(Nlog(logN)) |
| 55 | +# Create prefix sum array = O(N) |
| 56 | +# Fill prefix sum array with O(1) work per element using SPF = O(N) |
| 57 | +# Do M queries, with O(1) work per element using prefix sum array = O(M) |
| 58 | +# Overall: O(Nlog(logN) + 2 * N + M) = O(Nlog(logN) + M) |
| 59 | + |
| 60 | +def solution_b(n: int, p: list[int], q: list[int]) -> list[int]: |
| 61 | + """Optimised solution generated by gemini-3-pro (explained in comments)""" |
| 62 | + |
| 63 | + # spf[i] is the smallest prime factor for number i |
| 64 | + # If spf[i] == 0, then i is prime |
| 65 | + spf = [0] * (n+1) |
| 66 | + i = 2 |
| 67 | + while i * i <= n: |
| 68 | + # Sieve if i is prime, recording the smallest prime factor |
| 69 | + if spf[i] == 0: |
| 70 | + k = i * i |
| 71 | + while k <= n: |
| 72 | + if spf[k] == 0: |
| 73 | + spf[k] = i |
| 74 | + k += i |
| 75 | + i += 1 |
| 76 | + |
| 77 | + # prefix[i] is the number of semiprimes up to and including number i |
| 78 | + prefix = [0] * (n + 1) |
| 79 | + for num in range(2, n + 1): |
| 80 | + # Composite number (candidate for semiprime) |
| 81 | + if spf[num] != 0: |
| 82 | + smallest_prime_factor = spf[num] |
| 83 | + complement = num // smallest_prime_factor |
| 84 | + if spf[complement] == 0: |
| 85 | + prefix[num] += 1 |
| 86 | + prefix[num] += prefix[num - 1] |
| 87 | + |
| 88 | + # Why dividing by SPF can verify semiprimes correctly: |
| 89 | + # 1. If N is prime (single prime factor), we don't enter the conditional (... if spf[num] != 0:) |
| 90 | + # 2. If N is semiprime (2 prime factors), dividing by SPF[N] returns the other prime factor |
| 91 | + # 3. If N has more than 2 prime factors (every other number), dividing by SPF[N] |
| 92 | + # results in another composite number (not prime) |
| 93 | + |
| 94 | + return [prefix[q[i]] - prefix[p[i]-1] for i in range(len(p))] |
0 commit comments