2

找到幸运数字实际上是一个问题 - 那些数字之和和数字平方和为质数的数字。我已经实施了埃拉托色尼筛。现在为了进一步优化它,我评论了我的 getDigitSum 方法,我认为它很重并替换为两个硬编码值,但解决一个测试用例仍然需要几分钟。这是对实际问题的参考

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);
    }

}
}

我应该用什么来缓存结果,以便搜索所需的时间更少,目前它需要大量的时间。完成 10000 个范围为 1 99999999999999(18 次 9 - 最坏情况)的测试用例需要几分钟,即使搜索值已被硬编码以用于测试目的(1600、1501)。

4

2 回答 2

1

你需要一个不同的算法。缓存不是你的问题。

如果范围很大 - 你可以打赌有些会 - 即使是一个几乎什么都不做的循环也会花费很长时间。如果我理解正确,范围的结尾被限制为不超过 10 18 。假设范围的开始是一半。然后你会迭代 5*10 17 个数字。假设您有一个 2.5 GHz CPU,那么您每秒有 2.5*10 9个时钟周期。如果每次迭代需要一个周期,那就是 2*10 8 CPU 秒。一年大约有 3.1*10 7秒,因此循环大约需要六年半。

从另一边攻击问题。数字的平方和最多可以是 18*9 2,也就是 1458,一个相当小的数字。数字本身的总和最多可以是18*9 = 162

对于小于 162 的素数,找出所有可能的分解为最多 18 位数字的总和(忽略 0)。丢弃那些平方和不是素数的分解。剩下的组合不多。然后找出在指定范围内您可以使用每种可能的分解构造多少个数字(如果需要,用零填充)。

于 2012-10-09T22:28:07.817 回答
1

此实现中几乎没有可以改进的地方。为了开始解决问题,我首先做了一些更改以了解主要问题:使总起始案例的值为 1,并将范围设置为十亿(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+"");

以上不好。每次调用此方法时,都会创建两个 String 对象,例如 (sum+"") ,然后再将它们解析为 int。考虑到该方法在我的测试中被调用了十亿次,它产生了 20 亿次 String 对象创建操作。因为您知道该值是一个 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])将创建一个 Integer 对象(带有自动装箱),因为 contains 方法需要一个对象。这是一个很大的浪费,没有必要。在我们的示例中,将创建十亿个 Integer 对象。此外,集合的使用,无论是树集还是散列集,都不是我们的最佳选择。

您要做的是获取一个包含 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 秒。

十亿次迭代花费了 4 秒而不是 5 分 8 秒,这是一个改进。剩下的唯一问题是数字总和和数字平方和的实际计算。我注释掉的代码(如您在我发布的代码中所见)。如果取消注释,运行时间将需要 6-7 分钟。在这里,没有什么可以改进的,除非你找到一些数学方法来根据以前的结果进行增量计算。

于 2012-10-25T13:42:45.080 回答