0

I posted earlier trying to bruteforce it, but it didn't work. Here's my attempt #2 with recursion (first time using recursive methods). Please help!

Here's what happens: The code runs fine, with smaller numbers, but when we get up to one million, the code simply will run, and nothint at all happens. In Eclipse, it still gives me the option to end, but I've let it run for a very long time with nothing helping.

/**
* The following iterative sequence is defined for the set of positive
* integers:
*
* n → n/2 (n is even) n → 3n + 1 (n is odd)
*
* Using the rule above and starting with 13, we generate the following
* sequence: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
*
* It can be seen that this sequence (starting at 13 and finishing at 1)
* contains 10 terms. Although it has not been proved yet (Collatz Problem),
* it is thought that all starting numbers finish at 1.
*
* Which starting number, under one million, produces the longest chain?
*
* NOTE: Once the chain starts the terms are allowed to go above one
* million.
*/
public class Euler14 {
    static int desiredMax = 1000000;
    static int maxTerm = 0;
    static int maxNumberOfTerms = 0;
    static int currentNumber = 0;
    static int numberOfTerms = 0;
    public static void doMath(int startingNumber) {
        if(startingNumber == 1) {
            System.out.print( maxTerm + " " + maxNumberOfTerms);
        }
        else {
            currentNumber = desiredMax;
            while(currentNumber!= 1) {
                if(currentNumber%2 == 0) {
                    currentNumber = currentNumber/2;
                    numberOfTerms++;
                } else {
                    currentNumber = (3 * currentNumber) + 1;
                    numberOfTerms++;
                }
            }
            numberOfTerms++;
            if(numberOfTerms > maxNumberOfTerms) {
                maxNumberOfTerms = numberOfTerms;
                maxTerm = startingNumber;
            }
            desiredMax--;
            doMath(desiredMax);

        }
    }
    public static void main(String[] args) {

        doMath(desiredMax);
    }

}
Dici
  • 25,226
  • 7
  • 41
  • 82
Cflo
  • 45
  • 1
  • 4
  • 1
    What did you discover when you debugged the problem? – Oliver Charlesworth Jan 26 '15 at 22:31
  • 1
    Hum, is this really Euler n°7 or n°14 ? Whatever it is, it seems really complicated (loop + recursion) whereas I just checked my solutions for problems 7 and 14, they have 15 and 3 lines respectively (Java and Scala) – Dici Jan 26 '15 at 22:32
  • Is this 7 or 14? 7 is finding prime numbers – EDToaster Jan 26 '15 at 22:38
  • I edited your question, as it indeed is problem 14 and not 7. Also added the description of the problem as a comment – Dici Jan 26 '15 at 22:38

3 Answers3

3

There are many wrong things with your code :

  • use of a recursive method which is no more no less than a loop going downward
  • use of static variables
  • numberOfTerms never reinitialized
  • as pointed by azurefrog, you have an integer overflow which is causing an infinite loop.

I was rearranging your code with as few changes as possible when he came up with the answer, so all I can do now is to show you a working code very similar to yours. See how cleaner it is this way :

public class Euler14 {
    public static void main(String[] args) {
        int maxTerm = 1000000;
        int maxNumberOfTerms = 1;

        // this loop replaces your recursion, which is not needed here and quite costly even if it is tail-recursion
        for (int i = maxTerm ; i >= 2; i--) {
            int numberOfTerms = 0;
            // declare as long to prevent the overflow
            long currentNumber = i;
            while (currentNumber != 1) {
                if (currentNumber % 2 == 0)
                    currentNumber = currentNumber / 2;
                else
                    currentNumber = (3 * currentNumber) + 1;

                numberOfTerms++;
                if (numberOfTerms > maxNumberOfTerms) {
                    maxNumberOfTerms = numberOfTerms;
                    maxTerm = i;
                }
            }
        }
        System.out.println(maxTerm);
    }
}
Dici
  • 25,226
  • 7
  • 41
  • 82
  • Technically it's not infinite recursion (that can't happen, since eventually you overflow the stack). He's getting stuck in the inner while loop on `doMath(999167)` in an infinite arithmetic loop. – azurefrog Jan 26 '15 at 23:00
  • It would be interesting to see if this is fast enough. When I solved the problem, I used memoization so I wan't continually recalculating the sub-chains. – Andrew Shepherd Jan 26 '15 at 23:04
  • 1
    @AndrewShepherd Yeah it is bruteforcing (however it does not make any use of memory, which can be a good thing), but it runs in 501 ms, which is enough for Euler problems. Do you remember how fast your code is ? – Dici Jan 26 '15 at 23:07
  • @Dici - I wrote mine in Javascript, not Java, but running in a Chrome browser it takes 120 ms. – Andrew Shepherd Jan 26 '15 at 23:49
  • @AndrewShepherd okay, so it should be quite fast in Java – Dici Jan 26 '15 at 23:50
3

The main problem is that you are trying to do math on large numbers with ints. When your program gets down to a desiredMax of 999167, you're going into an infinite loop.

In Java, the largest value an int can represent is 2,147,483,647. When your algorithm gets to 999167, it quickly exceeds that limit. If you print the value of currentNumber in your inner while-loop, you see this:

...
1330496806
665248403
1995745210
997872605
-1301349480    <-- oops
-650674740
-325337370
...

You are trying to set currentNumber to 2,993,617,816, so your value is going to overflow.

This causes your while-loop to never terminate, since you don't account for negative numbers. You quickly settle into a repeating sequence of

-25
-74
-37
-110
-55
-164
-82
-41
-122
-61
-182
-91
-272
-136
-68
-34
-17
-50
-25
... ad infinitum

You could try switching to a bigger numerical representation (long), but, even if you switch to using long values, the way you are trying to recurse will cause a stack overflow long before you ever finish trying to evaluate a desiredMax of 1000000. (On my box, I get a StackOverflowError when I get down to 997474).

You need to go back and rethink the structure of your program. Recursion can be a useful tool, but it's dangerous to use unless you know that you aren't going to go too deep.

Community
  • 1
  • 1
azurefrog
  • 10,785
  • 7
  • 42
  • 56
1

This is a good example of where you can employ Memoization.

Below is a solution that uses recursion, but avoids the need to continually go over paths you've calculated already.

This also separates the chain-calculation code from the searching-for-the-maximum code.

public class Euler14 {
    static long[]   records = new long[1000000];

    // //////////////////////////////////////////////
    // Recursively calculates one chain length
    //
    static long getLength(long n) {
        // Terminating condition
        if (n == 1) {
            return n;
        }
        // Have we already calculated this?
        if ((n < records.length) && (records[(int) n] != 0)) {
            return records[(int) n];
        }
        // Haven't calculated this yet, so calculate it now
        long length = getLength(n % 2 == 0 ? n / 2 : 3 * n + 1) + 1;
        // Record the result for later use
        if (n < records.length) {
            records[(int) n] = length;
        }
        return length;
    }

    static long calculateQuestionFourteen() {
        long maxLength = 0;
        long maxStart = 0;
        for (long i = 1; i < 1000000; ++i) {
            long thisLength = getLength(i);
            if (thisLength > maxLength) {
                maxLength = thisLength;
                maxStart = i;
            }
        }
        return maxStart;
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        System.out.println(calculateQuestionFourteen());
        System.out.println(System.currentTimeMillis() - start);
    }
}
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • That does not compile, and when I fix it, does not work either. You have an `ArrayOutOfBoundsException`, your `length < records.length` check is useless because you then perform `records[n] = length` and not `records[length] = n` – Dici Jan 26 '15 at 23:45
  • 1
    Allow me to fix the compile errors, because it is neither Javascript nor Java right now. It runs fine now, in 32 ms. +1 for that – Dici Jan 27 '15 at 00:04