Thursday, July 9, 2009

Project Euler - Problem 51

This was a fun one.

By replacing the 1st digit of *57, it turns out that six of the possible values: 157, 257, 457, 557, 757, and 857, are all prime.

By replacing the 3rd and 4th digits of 56**3 with the same digit, this 5-digit number is the first example having seven primes, yielding the family: 56003, 56113, 56333, 56443, 56663, 56773, and 56993. Consequently 56003, being the first member of this family, is the smallest prime with this property.

Find the smallest prime which, by replacing part of the number (not necessarily adjacent digits) with the same digit, is part of an eight prime value family.


At first I came up with a method involving recursing iterator functions to generate masks and replace the digits, which worked great, except it was slow (80 seconds to find the answer!). It checked all possibilites, for all primes. While easily scalable (would work on numbers > 10 digits), it certainly wasn't practical.

So I then I boiled down the problem and simplified it a bit more. First off:

  • The number of replacement digits MUST equal 3. Why? Because the sum of the digits of a prime must not be evenly divisible by 3. Replacing digits one or two at a time allows for a maximum family size of 7. Since the sum of the digits of a prime when replaced 3 at a time will always not be divisble by 3, this is the only way to avoid this issue.
  • Second, generating digit masks and permutations is unnecessary. This is the biggest time saver. As long as the digit count is less than 10, we can simply go through a prime listing, and for each prime, replace digits that repeat (3 times per the above rule) with other digits and test for primality.
That said, here's the code. I create a boolean prime sieve, then iterate through primes. I perform a full set of digit replacements on each character that repeats 3 times, and count the result.

public static ValueType Go()
{
var primes = M.Primes(1000000);

for (int i = 10; i < 1000000; i++)
{
if (primes[i])
{
string strPrime = i.ToString();

foreach (char c in strPrime.ToCharArray().Distinct())
{
if (strPrime.Replace(c.ToString(), "").Length != strPrime.Length - 3)
continue;

var listPrimes = new List();
for (int j = 0; j < 10; j++)>
{
int newNum = int.Parse(strPrime.Replace(c.ToString(), j.ToString()));
if (primes[newNum] && newNum.ToString().Length == strPrime.Length)
listPrimes.Add(newNum);
}
if (listPrimes.Distinct().Count() == 8)
return listPrimes.Min();
}
}
}
return 0;
}

The result?
121313

which has family members:
222323,
323333,
424343,
525353,
626363,
727373,
828383,
929393

Voila! Code executes brilliantly, in 80ms.

No comments:

Post a Comment