Algorithm
Your current method relies on iterating through all numbers and checking if they're 1) a factor 2) prime.
With the new algorithm, not only are we able to skip the expensive primality check, but we're also able to reduce our search space massively (the upper bound is the square root of the number).
public static long maxPrimeFactor(long n) {
long factor = -1;
for (int i = 2; i <= (long) Math.sqrt(n); i++) {
if (n == 1) { break; }
if (n % i != 0) { continue; }
factor = i;
while (n % i == 0) {
n /= i;
}
}
return n == 1 ? factor : n;
}
This algorithm is a slightly optimized version of Martin R's answer.
We first "trim" down a number by repeatedly dividing it by factors of itself which are smaller or equal to its square root. Here're some examples
[3] (no factors <= sqrt(3))
[4 -> 2 -> 1] (factors: 2)
[200 -> 100 -> 50 -> 25] --> [5 -> 1] (factors: 2, 5)
[7007 -> 1001 -> 143] --> [13] (factors: 7, 11. 13 > sqrt(143), hence it stops)
The square brackets represent groups of "divisions" where a number is repeatedly divided by the factor.
Most composite numbers will be reduced down to 1. This is because all prime factors of a composite number will always be lower than or equal to its square root. However, note that as an optimization, we trim down the upper bound to the square root of the new number upon every iteration. This results in a few cases where we stop at a prime number. In that case, we return that number, which is the largest prime factor.
Note that we only need to iterate through primes. Since a prime sieve is too expensive, iterating through {2, 3, 5, 7, ..., sqrt(n)} works well enough for our scenario. For clarity, my code iterates through all integers from 2 to sqrt(n); I'll leave this to the OP as an exercise.
Benchmarks
In these benchmarks, my algorithm refers to the optimized version of the algorithm, as mentioned in the previous paragraph.
Result: 6857
Time required to calculate in nanoseconds: 870707572
Result: 6857
Time required to calculate in nanoseconds: 864074780
Slight improvement there, but not much.
Result: 6857
Time required to calculate in nanoseconds: 10038730
A definitely massive improvement, but is still a bit slow since the primality tests are too slow.
Result: 6857
Time required to calculate in nanoseconds: 179319
An even more massive improvement thanks to the primality test.
My algorithm:
Result: 6857
Time required to calculate in nanoseconds: 118748
Looks like it's faster than thepace's algorithm on first glance, but since these benchmarks are runned only once, it's hard to tell.
Running 1000 trials:
Let's try running 1000 trials with a multiple of 2 large primes:
$$3333593 \times 6882791 = 22944423898063$$
thepace's algorithm:
Result: 6882791
Time required to calculate in nanoseconds: 31844297317
My algorithm:
Result: 6882791
Time required to calculate in nanoseconds: 19238405860
Ooh, that seems like a massive improvement over thepace's algorithm.
Now, we'll try a large number of small primes multiplied together, using 1000 trials once again.
$$2^2 \times 3^9 \times 7^7 \times 13^2 = 10957822683444$$
thepace's algorithm:
Result: 1
Time required to calculate in nanoseconds: 8953229
Well, it broke... As it turns out, there're 2 errors in thepace's algorithm. Firstly, the isprime function does not detect 2 and 3 as primes. Secondly, the algorithm cannot handle repeated prime factors. This is why the algorithm works for our case in Project Euler but not in this case.
Here's my algorithm:
Result: 13
Time required to calculate in nanoseconds: 1589141
This one is pretty quick because my algorithm works better for small primes.
Testing product of first 15 primes which is:
$$2 \times 3 \times 5 \times 7 \times 11 \times 13 \times 17 \times 10 \times 23 \times 29 \times 31 \times 37 \times 41 \times 43 \times 47 = 614889782588491410$$
thepace's algorithm:
Result: 47
Time required to calculate in nanoseconds: 7346640
My algorithm:
Result: 47
Time required to calculate in nanoseconds: 2438296
Once again, my algorithm beats thepace's algorithm.
My original solution in Haskell is as follows:
module Main where
intSqrt :: Integer -> Integer
intSqrt = floor . sqrt . fromIntegral
maxPrimeFact :: Integer -> Integer
maxPrimeFact n = go n 1 (2 : [3, 5..intSqrt n])
where
go 1 maxPrime _ = maxPrime
go n _ [] = n
go n maxPrime wheel@(w:ws)
| m == 0 = go d w $ takeWhile (<= intSqrt d) wheel
| otherwise = go n maxPrime ws
where
(d, m) = n `divMod` w
problem3 :: Integer -> Integer
problem3 = maxPrimeFact
main :: IO ()
main = do
_ <- getLine
contents <- getContents
let cases = map read $ lines contents
let results = map problem3 cases
mapM_ print results
Credit must go to Martin R for the original solution.