5

I am currently attempting to solve a ProjectEuler problem and I have got everything down, except the speed. I am almost certain the reason the program executes so slowly is due to the nested loops. I would love some advice on how to speed this up. I am a novice programmer, so I am not familiar with a lot of the more advanced methods/topics.

public class Problem12 {

    public static void main(String[] args) {
        int num;

        for (int i = 1; i < 15000; i++) {
            num = i * (i + 1) / 2;
            int counter = 0;

            for (int x = 1; x <= num; x++) {
                if (num % x == 0) {
                    counter++;
                }
            }
            System.out.println("[" + i + "] - " + num + " is divisible by " + counter + " numbers.");
        }
    }
}

EDIT : Below is the new code that is exponentially faster. Removed the constant line printing as well to speed it up even more.

public class Problem12 {

    public static void main(String[] args) {
        int num;

        outerloop:
        for (int i = 1; i < 25000; i++) {
            num = i * (i + 1) / 2;
            int counter = 0;

            double root = Math.sqrt(num);
            for (int x = 1; x < root; x++) {
                if (num % x == 0) {
                    counter += 2;
                    if (counter >= 500) {
                        System.out.println("[" + i + "] - " + num + " is divisible by " + counter + " numbers.");
                        break outerloop;
                    }
                }
            }
        }
    }
}
jzbakos
  • 285
  • 1
  • 3
  • 12
  • 1
    Try using a faster language like C, and try using bitshifts instead of divisions and multiplications, if they're available in Java – Shahe Ansar Oct 11 '16 at 20:57
  • Does this algorithm work at all? so like if `i=3` then `num=6` and then `counter=4` which is incorrect – afsafzal Oct 11 '16 at 21:04
  • 4
    @ShaheAnsar how can you assume that C is faster than Java for this, if you don't know enough to know if bitshifts are supported? – njzk2 Oct 11 '16 at 21:07
  • @afsafzal you mean "which is correct". the values expected for 6 are `1, 2, 3, 6`, as visible in the description of the problem the OP linked to – njzk2 Oct 11 '16 at 21:09
  • 4
    You're going to print 15000 lines. Any print takes orders of magnitude more time than simple statements, so if you want to time it, first comment out the print. – Mike Dunlavey Oct 11 '16 at 21:15
  • @njzk2 Because every single Java app that I've used is horribly inefficient – Shahe Ansar Oct 11 '16 at 21:37

2 Answers2

5

For starters, when looking at divisors, you never need to go further than the root square of the number, because each divisor below the square root has an equivalent above.

n = a * b => a <= sqrt(n) or b <= sqrt(n)

Then you need to count the other side of the division:

double root = Math.sqrt(num);
for (int x = 1; x < root; x++) {
    if (num % x == 0) {
        counter += 2;
    }
}

The square root is special because it counts only once if it is integer:

if ((double) ((int) root) == root) {
    counter += 1;
}
njzk2
  • 38,969
  • 7
  • 69
  • 107
  • This did the trick. However, I am not sure how. Would you mind elaborating a bit more on why this works? – jzbakos Oct 11 '16 at 21:20
  • 1
    each divisor works as a pair, which is why we count each twice. For each pair, one of the element is below the square root, the other one is above. This is easy to see: if both elements are above, the result is above as well. If both are below, the result is below as well. Example: `10 = 2*5 = 1*10`. 2 and 1 are below 3.16, 5 and 10 are above. – njzk2 Oct 11 '16 at 21:30
  • althoug, I was wrong about there being no triangular _and_ square numbers, editing now – njzk2 Oct 11 '16 at 21:34
0

You just need to factorize the number. p^a * q^b * r^c has (a+1)*(b+1)*(c+1) divisors. Here is some basic implementation using this idea:

static int Divisors(int num) {
    if (num == 1) {
        return 1;
    }

    int root = (int) Math.sqrt(num);
    for (int x = 2; x <= root; x++) {
        if (num % x == 0) {
            int c = 0;
            do {
                ++c;
                num /= x;
            } while (num % x == 0);
            return (c + 1) * Divisors(num);
        }
    }

    return 2;
}

public static void test500() {
    int i = 1, num = 1;
    while (Divisors(num) <= 500) {
        num += ++i;
    }

    System.out.println("\nFound: [" + i + "] - " + num);
}
Antonín Lejsek
  • 6,003
  • 2
  • 16
  • 18