计算给定范围内的半素数[a..b]

时间:2018-12-23 09:36:31

标签: java algorithm performance

我正在解决Codility问题CountSemiprimes: Count the semiprime numbers in the given range [a..b]

任务说明

一个素数是一个正整数X,其正好有两个不同的除数:1和X。前几个素数是2、3、5、7、11和13。

半素数是一个自然数,它是两个(不一定是不同的)素数的乘积。前几个半素数是4、6、9、10、14、15、21、22、25、26。

为您提供了两个非空数组P和Q,每个数组由M个整数组成。这些数组表示有关指定范围内的半素数的查询。

查询K要求您查找(P [K],Q [K])范围内的半素数,其中1≤P [K]≤Q [K]≤N。

为以下假设写出有效的算法:

  • N是[1..50,000]范围内的整数;
  • M是[1..30,000]范围内的整数;
  • 数组P,Q的每个元素是[1..N]范围内的整数; P [i]≤Q [i]。

我的解决方案

我目前的分数是66%,问题在于大型数据集的性能:

  • 大随机,长度=〜30,000
  • 所有最大范围

测试表明,该过程大约需要2秒钟,但是我的解决方案需要7秒钟以上。

这是我目前的解决方案

class Solution {
    private static List<Integer> getPrimes(int max) {
        List<Integer> primes = new ArrayList<>(max / 2);

        for (int i = 0; i < max; i++)
            if (isPrime(i))
                primes.add(i);

        return primes;
    }

    private static boolean isPrime(int val) {
        if (val <= 1)
            return false;
        if (val <= 3)
            return true;

        for (int i = 2, sqrt = (int)Math.sqrt(val); i <= sqrt; i++)
            if (val % i == 0)
                return false;

        return true;
    }

    private static boolean[] getSemiPrimes(int N) {
        List<Integer> primes = getPrimes(N);
        boolean[] semiPrimes = new boolean[N + 1];

        for (int i = 0; i < primes.size(); i++) {
            if (primes.get(i) > N)
                break;

            for (int j = i; j < primes.size(); j++) {
                if (primes.get(j) > N || N / primes.get(i) < primes.get(j))
                    break;

                int semiPrime = primes.get(i) * primes.get(j);

                if (semiPrime <= N)
                    semiPrimes[semiPrime] = true;
            }
        }

        return semiPrimes;
    }

    public static int[] solution(int N, int[] P, int[] Q) {
        boolean[] semiPrimes = getSemiPrimes(N);
        int[] res = new int[P.length];

        for (int i = 0; i < res.length; i++)
            for (int j = P[i]; j <= Q[i]; j++)
                if (semiPrimes[j])
                    res[i]++;

        return res;
    }
}

关于提高绩效的任何想法吗?我的最后一个是删除Set来保存带有数组的半素数。它帮助我解决了一些性能测试。

12 个答案:

答案 0 :(得分:1)

您可以预先计算大小为N + 1的数组A,该数组在A [i]中存储小于或等于i的半素数。然后可以立即计算查询p, q:p和q(含)之间的半素数为A[q] - A[p-1]

可以有效地计算此数组:令P为小于或等于N / 2的素数数组。然后(使用类似Java的伪代码):

A = new int[N+1]
for (int p : P) {
  for (int q : P) {
      if (p*q > N || q > p) break;
      A[p*q] = 1
  }
}

for (int i = 1; i <= N; i++)
    A[i] += A[i-1]

这可以通过在数组中用1标记半质数,然后取一个累加和来实现。它的运行时间比O(N ^ 2)好,并且比O(N)时间差-N/2logN中大约有P个素数,因此第一部分是O((N / logN)^ 2 ),总和为O(N)。 [注意:由于内循环的尽早终止,所以我猜第一部分的复杂度比O((N / log N)^ 2)好,但是我没有证明这一点。使用戊二烯筛子计算P中的质数为O(N log log N)。

此程序的Python版本需要0.07秒的时间才能为A预先计算N=50000,并执行30000个查询。在codility上运行时,它获得满分(100),codility报告它检测到代码具有复杂度O(N log(log(N))+ M)。

答案 1 :(得分:0)

这是一个有趣的问题。我尝试过,并获得88%的分数。

这是我的策略:

  • 我用Sieve of Eratosthenes获得了素数的BitSet
  • 现在,我遍历了BitSet并将所有素数添加到primeList中。
  • 我寻找半素数的策略有点有趣,因此我逐步采用了这种策略。
private static boolean isSemiPrime(int n) {
    if(n==1 || n==0 || primeBitSet.get(n))
        return false;
    int firstFactor = findFirstFactor(n);
    if(firstFactor==0 || firstFactor==1)
        return false;
    return isPrime(n / firstFactor);
}

private static int findFirstFactor(int n) {

    for (int i = 0; i < primeList.size(); i++) {
        if (n % primeList.get(i) == 0)
            return primeList.get(i);
    }
    // should never be the case
    return 0;
}

我不太确定为什么我得到88%的分数。 (我所缺少的)

但是最有趣和值得注意的部分是检查给定数字是否为半素数的策略:

  • 找到给定数字的第一个素数
  • 然后检查给定数字和第一素数因子的商是否是素数。
  • 如果是素数,则给定的数字是半素数,否则给定的数字不是半素数。

请注意,我还做过一种非常幼稚的簿记形式,其中我制作了一个累积数组,用于存储直到索引x为止的半素数的总数。一次填充此数组并回答O(1)中的每个查询都是显而易见的优化。

与解决方案无关,但我的Task Score是88%,Correctness是100%,Performance是80%。我很高兴听到建议和我错过的任何事情。

希望这会有所帮助。 :)

答案 2 :(得分:0)

const isSemiPrime = (num) => {
    let cnt = 0
    for (let i = 2; cnt < 2 && i * i <= num; ++i) {
        while (num % i == 0) {
            num /= i
            ++cnt
        }
    }
    if (num > 1)++cnt
    return cnt == 2 ? true : false
}

console.log(
    [4, 6, 9, 10, 14, 15, 21, 22, 25, 26, 33, 34, 35, 38, 39, 46, 49, 51, 55].filter(isSemiPrime)
        .length
)

答案 3 :(得分:0)

我的解决方案使用Eratosthenes筛网,以便将最小N个素数存储在Factor [N]数组中。 然后,如果Factor [N / Factor [N]] = 0,我们有一个半素数递增和扫描。 然后,返回数组的条目r将是: A [r] = Inclusive_scan [Q [r]]-Inclusive_scan [P [r] -1]。

以下是相应的python代码(任务分数为100%):

def solution(N, P, Q):
 A=len(P)*[0]
 if N<4:
     return A
#Minimum prime factor of n stored in Factor[n]
 Factor = [0] * (N + 1)
 i = 2
 while (i * i <= N):
  if (Factor[i] == 0):
   k = i * i
   while (k <= N):
    if (Factor[k] == 0):
     Factor[k] = i;
    k += i
  i += 1
#Count semi prime numbers and store 
#sum scan in array Incluse_scan   
 Incluse_scan=[0] * (N + 1)
 cnt_semi=0
 for k in range(4,N+1):
     if Factor[k]!=0:
         d=int(k/Factor[k])
         if Factor[d]==0:
             cnt_semi+=1                 
     Incluse_scan[k]=cnt_semi   
#Do the difference of semi prime counters
 for r in range(0,len(P)):
     if(P[r]<=4):
       min_inclusive=0
     else:
       min_inclusive=P[r]-1 
     A[r]=Incluse_scan[Q[r]]-Incluse_scan[min_inclusive] 
 return A

答案 4 :(得分:0)

Ruby 100%解决方案

require 'prime'
require 'set'

def solution(n, p, q)
    primes = Prime::EratosthenesGenerator.new.take_while {|i| i <= n/2 }
    sqrt = Math.sqrt(n)
    semiprimes = primes.each_with_index.inject(Set.new) do |acc, (e,i)|
      break acc if e > sqrt.to_i
      primes[i..-1].each{ |pr| e*pr > n ? break : acc << e*pr }
      acc
    end
    offsets = semiprimes.sort.each_with_index.inject([]) {|acc,(el,i)| acc[el] = i+1;acc  }

    p.each_with_index.inject([]) do |acc, (el,i)|
      next acc << 0 unless offsets[el..q[i]]

      left =  offsets[el..q[i]].detect{|a| a}
      next acc << 0 unless left

      right = offsets[el..q[i]].reverse_each.detect{|a| a}

      acc << ((left..right).size)
    end
end

答案 5 :(得分:0)

得分为100%的Java解决方案如下:

  • 找到素数集不大于N

  • 的素数
  • 根据它们从0和1的按位排列创建半素

  • 创建半素数的前缀和

  • 计算P[i]中从Q[i]O(M)的查询

整个算法是O(N * log(log(N)) + M),由Codility的测试结果评估得出。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CountSemiPrime {

    public static void main(String[] args) {
        int[] P = new int[] {1, 4, 16};
        int[] Q = new int[] {26, 10, 20};
        System.out.println( Arrays.toString( new CountSemiPrime().solution( 26, P, Q ) ) );
    }

    public int[] solution(int N, int[] P, int[] Q) {

        Integer[] primes = sieve(N/2+1);

        int[] temp = new int[N+1];
        for (int i = 0; i < primes.length; i++) {
            for (int j = 0; j < primes.length; j++) {
                int semiPrime = primes[i] * primes[j];
                if(semiPrime <= N)
                    temp[semiPrime] = 1;
            }
        }

        int[] prefix = new int[N+1];
        for (int i = 1; i < temp.length; i++) {
            prefix[i] = temp[i] + prefix[i-1];
        }

        int[] retVal = new int[P.length];
        for (int i = 0; i < retVal.length; i++) {
            retVal[i] = prefix[Q[i]] - prefix[P[i]-1];
        }

        return retVal; 
    }


    public Integer[] sieve(int n) {

        boolean[] temp = new boolean[n+1];
        for (int i = 0; i < temp.length; i++) {
            temp[i] = true;
        }
        temp[0] = temp[1] = false;

        int i = 2;
        while (i * i <= n) {
            removeProducts( temp, i );
            i++;
        }

        List<Integer> ret = new ArrayList<>();
        for (int j = 0; j < temp.length; j++) {
            if(temp[j])
                ret.add( j );
        }

        return ret.toArray( new Integer[ret.size()] );
    }

    private void removeProducts(boolean[] temp, int i) {
        for (int j = i*i; j < temp.length; j++) {
            if(temp[j] && j % i == 0) {
                temp[j] = false;
            }
        }
    }
}

答案 6 :(得分:0)

这里是解决方案的Javascript版本,但它是55%:

function solution(N, P, Q) {

  function isPrime(num) {
    for(var i = 2; i < num; i++)
      if(num % i === 0) return false;
    return num > 1;
  }

  const min = Math.min(...P)
  const max = Math.max(...Q)
  const A = []
  for(let i=min;i<max;i++) {
      for(let j=min;j<max;j++) {
          if (isPrime(i) && isPrime(j)) {
              const prod = j * i
              if (prod > max) break
              if (A.includes(prod)) continue
              A.push(j * i)
          }
      }
  }

  const result = []
  for(let i=0;i<P.length;i++) {
      for(let j=P[i];j<=Q[i];j++) {
          result[i] = result[i] || 0
          if (A.includes(j)) {
              result[i]++
          }
      }
  }
  return result
}

答案 7 :(得分:0)

我想提一下,您用于查找素数的方法效率低下。

您的代码:

private static List<Integer> getPrimes(int max) {
    List<Integer> primes = new ArrayList<>(max / 2);

**    for (int i = 0; i < max; i++)
**        if (isPrime(i))
**            primes.add(i);

    return primes;
}

private static boolean isPrime(int val) {
    if (val <= 1)
        return false;
    if (val <= 3)
        return true;

**    for (int i = 2, sqrt = (int)Math.sqrt(val); i <= sqrt; i++)
**        if (val % i == 0)
**            return false;

    return true;
}

我已标记要注意的行。 我会做这样的事情:

private static List<Integer> getPrimes(int max) {
    List<Integer> primes = new ArrayList<>(max / 2);
    primes.add(2);

    for (int i = 3; i < max; i++)
        if (isPrime(i, primes))
            primes.add(i);

    return primes;
}

private static boolean isPrime(int val, List<Integer> primes) {
    int sqrtv = Math.sqrt(val);
    for (int i = 0; i < primes.length(); i++)
    {
        int prime = primes.get(i);
        if (val % primes.get(i) == 0)
        {
            return false;
        } else if (prime > sqrtv) {
            return true;
        }
    }

    return true;
}

这是基于以下事实:

  1. 对isPrime的唯一调用来自getPrimes。 getPrimes将始终以升序调用val。
  2. 在使用参数val调用isPrime时,getPrimes已经获得了所有小于val的素数的列表。
  3. 在确定素数时没有必要除以非素数。如果我们已经知道数字'a'不能被2整除,那为什么还要麻烦将其除以4、6、8或10?如果我们知道它不能被3整除,那么它就不会被9整除..​​.因此所有非素数检查都通过使用先前计算的素数进行过滤而仅用于执行检查。

答案 8 :(得分:0)

这是我100%的c ++。我正在使用prefixSum。时间复杂度O(N * log(log(N))+ M)。

#include <iostream>
#include <vector>
#include <cmath>

using namespace std;

vector<int> solution(int N, vector<int> &P, vector<int> &Q)
{
    vector<bool> sieve(N, true);
    vector<int> ret;
    sieve[0] = sieve[1] = false;
    int i = 2;

    while (i * i <= N)
    {
        if (sieve[i])
        {
            int k = i * i;
            while (k <= N)
            {
                sieve[k] = false;
                k += i;
            }
        }
        i++;
    }

    vector<int> prefixSum(N + 1, 0);
    
    for (int i = 2; i <= sqrt(N); i++)
        if (sieve[i])
            for (int j = i; j <= N; j++)
            {
                if (j * i > N)
                    break;

                if (sieve[j])
                    prefixSum[j * i]++;
            }

    int carry;
    for (unsigned int i = 5; i < prefixSum.size(); i++)
    {
        carry = prefixSum[i - 1];
        prefixSum[i] += carry;
    }

    for (unsigned int i = 0; i < P.size(); i++)
        ret.push_back(prefixSum[Q[i]] - prefixSum[P[i] - 1]);

    return ret;
}

答案 9 :(得分:0)

100%的溶液破裂了。 https://app.codility.com/demo/results/trainingGVNHKU-MA5/

首先使用Eratosthenes筛子锻炼素数。

def get_sieve(n):
    # Use the sieve or Eratosthenes to produce an array of primes 
    # where factor[n] == 0 indicates a prime number
    factors = [0] * (n+1)
    i=2
    i2 = i*i
    while (i2 <= n):
        if not factors[i]:
            k = i2
            while k <= n:
                if not factors[k]:
                    factors[k] = i
                k += i
        i += 1
        i2 = i*i
    return factors

接下来,确定数字是否为半素数。如果两个因素都是素数,则它的半素数。

def is_semi_prime(n, factors):
    if factors[n]: # Check its not a prime
        for r in range(int(n**.5)+1, 1, -1):
            if not n%r:
                d = n//r
                return (not factors[d]) and (not factors[r])
    return False

然后扫描最多N个数字的范围,以计算增加的​​半素数的斜率。只需测量切片内的斜率,即可查看在该切片内发生了多少个半素数。

def solution(N, P, Q):
    # produce a slope of increasing semi primes
    factors = get_sieve(N)
    slope = [0] * (N+1)
    for i in range(1, N+1):
        slope[i] = slope[i-1] + is_semi_prime(i, factors) # Auto casting!! :-)
    # Optimus Prime!
    # print(list(enumerate(slope)))
    return [slope[Q[j]] - slope[P[j]-1] for j in range(len(P))]

https://github.com/niall-oc/things/blob/master/codility/count_semiprimes.py 还有更多 https://github.com/niall-oc/things/blob/master/codility/

答案 10 :(得分:0)

这是我100%的C ++解决方案。您可以在cpp的github中找到其他答案:


vector<int> getFactArr(int n) {
    vector<int> f(n+1, 0);
    f[1] = 1;
    int i = 2;
    while (i * i <= n) {
        if (f[i] == 0) {
            int k = i * i;
            while (k <= n) {
                if (f[k] == 0)
                    f[k] = i;
                k+=i;
            }
        }
        i++;
    }

    return f;
}

vector<int> solution(int N, vector<int> &P, vector<int> &Q) {
    vector<int> F = getFactArr(N);
    vector<int> prefix_semi_primes(N + 1, 0);

    for (int x = 1; x <= N; x++) {
        if (F[x] > 0 && F[x / F[x]] == 0)
            prefix_semi_primes[x]++;
        prefix_semi_primes[x] += prefix_semi_primes[x - 1];
    }

    const int M = P.size();
    vector<int> ans(M, 0);
    for (int i = 0; i < M; i++) {
        ans[i] = prefix_semi_primes[Q[i]] - prefix_semi_primes[P[i] - 1];
    }

    return ans;
}

答案 11 :(得分:0)

我采取了稍微不同的方法。该线程中的其他有效解决方案构建了一个常规的 Eratosthenes (F) 筛分法,并记录槽中的最小素数因子,因此半素数是那些 F[x] > 0 和 F[x // F[ x]] == 0,即除以最小的质因数产生另一个质数。

我的方法有点慢,但不使用除法,并构建了一个有趣的中间体:一个筛子,它可以准确计算有多少个因子构成了数字的素数分解(以及素数处的零)。对于每个素数 p,我会在位置 2p、3p、4p、... 处增加筛子,但也会为 p^2、2p^2、3p^2...、p^3、2p^3 计算一个因子, 3p^3、4p^3、...等等。 16 的槽存储值 4(质数分解:2*2*2*2),因为槽被 2、2^2、2^3 和 2^4 的访问命中。

那么半质数就是那些正好有 2 个质因数的位置。

之后,我构建了一个半素数的前缀计数,用于在恒定时间内回答查询。

{{1}}