2

ラッキーナンバー、つまり桁の合計と桁の二乗の合計が素数である数を見つけることは実際には問題です。エラトステネスのふるいを実装しました。さらに最適化するために、getDigitSumメソッドにコメントしました。これは、重く、ハードコードされた2つの値に置き換えられたと思いますが、1つのテストケースを解決するにはまだ数分かかります。これが実際に尋ねられた問題への参照です

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.TreeSet;

public class Solution {

private static int[] getDigitSum(long num) {

    long sum = 0;
    long squareSum = 0;
    for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
        if (tempNum < 0) {
            sum = sum + tempNum;
            squareSum = squareSum + (tempNum * tempNum);
        } else {
            long temp = tempNum % 10;
            sum = sum + temp;
            squareSum = squareSum + (temp * temp);

        }
    }
    int[] twosums = new int[2];
    twosums[0] = Integer.parseInt(sum+"");
    twosums[1] = Integer.parseInt(squareSum+"");
    // System.out.println("sum Of digits: " + twoDoubles[0]);
    // System.out.println("squareSum Of digits: " + twoDoubles[1]);
    return twosums;
}

public static Set<Integer> getPrimeSet(int maxValue) {
    boolean[] primeArray = new boolean[maxValue + 1];
    for (int i = 2; i < primeArray.length; i++) {
        primeArray[i] = true;
    }
    Set<Integer> primeSet = new TreeSet<Integer>();
    for (int i = 2; i < maxValue; i++) {
        if (primeArray[i]) {
            primeSet.add(i);
            markMutiplesAsComposite(primeArray, i);
        }
    }

    return primeSet;
}

public static void markMutiplesAsComposite(boolean[] primeArray, int value) {
    for (int i = 2; i*value < primeArray.length; i++) {
        primeArray[i * value] = false;

    }
}

public static void main(String args[]) throws NumberFormatException,
        IOException {
    // getDigitSum(80001001000l);
    //System.out.println(getPrimeSet(1600));
    Set set = getPrimeSet(1600);
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int totalCases = Integer.parseInt(br.readLine());
    for (int cases = 0; cases < totalCases; cases++) {
        String[] str = br.readLine().split(" ");
        long startRange = Long.parseLong(str[0]);
        long endRange = Long.parseLong(str[1]);
        int luckyCount = 0;
        for (long num = startRange; num <= endRange; num++) {
            int[] longArray = getDigitSum(num); \\this method was commented for testing purpose and was replaced with any two hardcoded values
            if(set.contains(longArray[0]) && set.contains(longArray[1])){
                luckyCount++;
            }


        }
        System.out.println(luckyCount);
    }

}
}

検索にかかる時間を短縮するために結果をキャッシュするために使用する必要があるものですが、現在は非常に時間がかかりません。検索値がテスト目的でハードコーディングされている場合でも、範囲1 99999999999999(18 x 9-最悪のケース)で10000のテストケースを完了するのに数分かかります(1600、1501)。

4

2 に答える 2

1

別のアルゴリズムが必要です。キャッシングはあなたの問題ではありません。

範囲が広い場合(そして、ある程度はそうなることは間違いありません)、ほとんど何もしないループでさえ、非常に長い時間がかかります。私が正しく理解していれば、範囲の終わりは1018以下に制限されています範囲の開始がその半分であると仮定します。次に、 5 *1017個の数値を繰り返します。2.5 GHzのCPUを使用しているとすると、1秒あたり2.5 *109クロックサイクルになります。各反復に1サイクルかかる場合、2 * 108CPU秒になります。1年は約3.1*10 7秒なので、ループには約6年半かかります。

反対側から問題を攻撃します。桁の2乗の合計は、最大で18 * 9 2、つまり1458であり、かなり小さい数です。数字自体の合計は最大で18*9 = 162

162未満の素数の場合、可能なすべての分解を最大18桁の合計として求めます(0を無視します)。二乗和が素数ではない分解を破棄します。あまり多くの組み合わせが残っていません。次に、可能な分解のそれぞれを使用して、指定された範囲内でいくつの数値を作成できるかを調べます(必要に応じてゼロを入力します)。

于 2012-10-09T22:28:07.817 に答える
1

この実装には、改善できる場所がいくつかあります。問題への攻撃を開始するために、最初にいくつかの変更を加えて、主な問題を把握しました。開始ケースの合計を値1にし、範囲を10億(1,000,000,000)に設定して、大量の反復を行いました。また、メソッド「getDigitSum」を使用しますが、実際に数字の合計を作成するコードをコメントアウトして、残りがどのように実行されるかを確認します。最初のテスト実行用に変更されたメソッドは次のとおりです。

private static int[] getDigitSum(long num) {

    long sum = 0;
    long squareSum = 0;
//    for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
//        if (tempNum < 0) {
//            sum = sum + tempNum;
//            squareSum = squareSum + (tempNum * tempNum);
//        } else {
//            long temp = tempNum % 10;
//            sum = sum + temp;
//            squareSum = squareSum + (temp * temp);
//
//        }
//    }
    int[] twosums = new int[2];
    twosums[0] = Integer.parseInt(sum+"");
    twosums[1] = Integer.parseInt(squareSum+"");
    // System.out.println("sum Of digits: " + twoDoubles[0]);
    // System.out.println("squareSum Of digits: " + twoDoubles[1]);
    return twosums;
}

public static void main(String args[]) throws NumberFormatException,
        IOException {
    // getDigitSum(80001001000l);
    //System.out.println(getPrimeSet(1600));
    Set set = getPrimeSet(1600);
    //BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int totalCases = 1;
    for (int cases = 0; cases < totalCases; cases++) {
        //String[] str = br.readLine().split(" ");
        long startRange = Long.parseLong("1");
        long endRange = Long.parseLong("1000000000");
        int luckyCount = 0;
        for (long num = startRange; num <= endRange; num++) {
            int[] longArray = getDigitSum(num); //this method was commented for testing purpose and was replaced with any two hardcoded values
            if(set.contains(longArray[0]) && set.contains(longArray[1])){
                luckyCount++;
            }


        }
        System.out.println(luckyCount);
    }

} 

コードの実行には5分8秒かかります。 これで、段階的に最適化を開始できます。ここで、最適化できる実装のさまざまなポイントについて説明します。

1-メソッドgetDigitSum(long num)

int[] twosums = new int[2];
twosums[0] = Integer.parseInt(sum+"");
twosums[1] = Integer.parseInt(squareSum+"");

上記は良くありません。このメソッドを呼び出すたびに、intに解析される前に、2つのStringオブジェクト(たとえば(sum + ""))が作成されます。私のテストではこのメソッドが10億回呼び出されていることを考えると、20億回の文字列オブジェクト作成操作が生成されます。値がintであることがわかっているので(そこにある計算と、提供したリンクに基づく)、キャストを使用するだけで十分です。

twosums[0] = (int)sum;
twosums[1] = (int)squareSum;

2-「メイン」方式では、次のようになります

for (long num = startRange; num <= endRange; num++) {
            int[] longArray = getDigitSum(num); \\this method was commented for testing purpose and was replaced with any two hardcoded values
            if(set.contains(longArray[0]) && set.contains(longArray[1])){
                luckyCount++;
            }
   }

ここにいくつかの問題があります:

a- set.contains(longArray [0])は、containsメソッドがオブジェクトを必要とするため、(オートボクシングを使用して)整数オブジェクトを作成します。これは大きな無駄であり、必要ありません。この例では、10億個の整数オブジェクトが作成されます。また、セットの使用法は、それがツリーセットであるかハッシュセットであるかにかかわらず、私たちの場合には最適ではありません。

あなたがしようとしているのは、1 .. 1600の範囲の素数を含むセットを取得することです。このように、範囲の数が素数であるかどうかを確認するには、それがセットに含まれているかどうかを確認します。set containsメソッドへの呼び出しが数十億あるため、これは適切ではありません。代わりに、セットを埋めるときに作成したブール配列を使用できます。数値1500が素数であるかどうかを確認するには、配列のインデックス1500にアクセスするだけです。これははるかに高速なソリューションです。その唯一の1600要素(1600は最悪の場合の桁の最大平方和よりも大きい)なので、誤った場所のメモリの浪費は、速度の向上と比較して問題にはなりません。

b- int [] longArray = getDigitSum(num); int配列が割り当てられ、返されます。それは何十億回も起こります。この場合、ループの外側で一度定義して、それがいっぱいになるメソッドに送信できます。数十億回の反復で、これは7秒節約しましたが、それ自体による大きな変化ではありません。ただし、計画どおりにテストケースを1000回繰り返すと、7000秒になります。

したがって、上記のすべてを実装するようにコードを変更した後、次のようになります。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.TreeSet;

public class Solution {

private static void getDigitSum(long num,int[] arr) {

    long sum = 0;
    long squareSum = 0;
//    for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
//        if (tempNum < 0) {
//            sum = sum + tempNum;
//            squareSum = squareSum + (tempNum * tempNum);
//        } else {
//            long temp = tempNum % 10;
//            sum = sum + temp;
//            squareSum = squareSum + (temp * temp);
//
//        }
//    }
    arr[0] = (int)sum;
    arr[1] = (int)squareSum;
    // System.out.println("sum Of digits: " + twoDoubles[0]);
    // System.out.println("squareSum Of digits: " + twoDoubles[1]);

}

public static boolean[] getPrimeSet(int maxValue) {
    boolean[] primeArray = new boolean[maxValue + 1];
    for (int i = 2; i < primeArray.length; i++) {
        primeArray[i] = true;
    }

    for (int i = 2; i < maxValue; i++) {
        if (primeArray[i]) {
            markMutiplesAsComposite(primeArray, i);
        }
    }

    return primeArray;
}

public static void markMutiplesAsComposite(boolean[] primeArray, int value) {
    for (int i = 2; i*value < primeArray.length; i++) {
        primeArray[i * value] = false;

    }
}

public static void main(String args[]) throws NumberFormatException,
        IOException {
    // getDigitSum(80001001000l);
    //System.out.println(getPrimeSet(1600));
    boolean[] primeArray = getPrimeSet(1600);
    //BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    int totalCases = 1;
    for (int cases = 0; cases < totalCases; cases++) {
        //String[] str = br.readLine().split(" ");
        long startRange = Long.parseLong("1");
        long endRange = Long.parseLong("1000000000");
        int luckyCount = 0;
        int[] longArray=new int[2];
        for (long num = startRange; num <= endRange; num++) {
            getDigitSum(num,longArray); //this method was commented for testing purpose and was replaced with any two hardcoded values
            if(primeArray[longArray[0]] && primeArray[longArray[1]]){
                luckyCount++;
            }


        }
        System.out.println(luckyCount);
    }

}
}

コードの実行には4秒かかります。

10億回の反復には、5分8秒ではなく4秒かかります。これは、改善です。残っている唯一の問題は、桁の合計と桁の二乗の合計の実際の計算です。私がコメントアウトしたそのコード(私が投稿したコードでわかるように)。コメントを外すと、実行時間は6〜7分かかります。ここでは、以前の結果に基づいて増分計算を行う数学的な方法を見つけた場合を除いて、改善することは何もありません。

于 2012-10-25T13:42:45.080 に答える