Problem 49 — Prime Permutations

solved Thu, 28 Nov 2019, 12:36 · level 1 · difficulty 4% projecteuler.net github

The arithmetic sequence, $1487, 4817, 8147$, in which each of the terms increases by $3330$, is unusual in two ways: (i) each of the three terms are prime, and, (ii) each of the $4$-digit numbers are permutations of one another.

There are no arithmetic sequences made up of three $1$-, $2$-, or $3$-digit primes, exhibiting this property, but there is one other $4$-digit increasing sequence.

What $12$-digit number do you form by concatenating the three terms in this sequence?


Problem Analysis

This problem layers two independent constraints over the integers: primality and digit-permutation equivalence. I am hunting for three n-digit primes that (i) form an arithmetic progression - three values equally spaced, so the middle term minus the first equals the last minus the middle - and (ii) are permutations of one another, meaning they use the exact same multiset of decimal digits. For the n = 4 case the puzzle hands us one sequence (1487, 4817, 8147) and asks for the other; the generalized n-digit version makes for an instructive exhaustive-search exercise.

Two observations make the search tractable. First, two numbers are permutations of each other if and only if their sorted digits are identical. This gives a canonical key: I can partition the n-digit primes into groups sharing a sorted-digit signature, then search each group independently, because an arithmetic triple can only ever live inside one group. Each group has at most n! members, which is tiny. Second, within a small set S of integers, finding every arithmetic triple does not require the naive O(|S|3) scan over all triples; bucketing pairwise differences finds them in O(|S|2). To enumerate primes cheaply I use a Sieve of Sundaram. By the prime number theorem there are on the order of 10n/n primes below 10n, so the sieve dominates the asymptotic cost while the per-group permutation and bucketing work is negligible.

Solution Approach

Permutation Grouping with Difference Bucketing (Python and C, index 0)

Both files implement the same algorithm. I describe it once and call out language differences afterward.

Step 1 - Sieve the primes. I generate all primes below 10n with the Sieve of Sundaram. The sieve works only on odd candidates: it marks index k as composite when k = i + j + 2ij for some i, j >= 1, and every surviving index k corresponds to the prime 2k + 1 (with 2 added separately). I then keep only the n-digit primes - those strictly greater than 10n-1.

Step 2 - Enumerate permutation families. For each n-digit prime p, I generate every permutation of its digits. For each permuted digit string I keep it only if it has no leading zero and is itself a known n-digit prime, building a set of "siblings" of p. If a family has fewer than three members it cannot contain a triple, so I skip it immediately - an early prune that discards most candidates.

Step 3 - Difference bucketing. Within each sibling family I consider every pair (a, b) with a < b, compute the difference d = b - a, and add both a and b into the bucket keyed by d. If a bucket ends up holding exactly three distinct values {x, y, z}, those three form an arithmetic progression with common difference d. The reasoning: if x < y < z is an arithmetic progression then y - x = z - y = d, so both pairs (x, y) and (y, z) land in bucket d and together populate it with exactly those three members. I record each triple in canonical sorted, space-joined form into a deduplication set, since different starting primes in the same family discover the same triple.

Language differences. The Python solution leans on the standard library: itertools.permutations for permutation generation, a native set for membership and deduplication, and collections.defaultdict(set) for the difference buckets. This keeps the core logic to a handful of lines. The C solution must build all of this by hand: it stores the n-digit primes as a sorted array of strings queried by binary search, generates permutations with Heap's algorithm, uses parallel flat arrays for difference buckets, and does linear-scan deduplication on the small result list. The benchmarks show C at 2.5 ms versus Python at 8.5 ms for n = 4, and 147 ms versus 408 ms for n = 5 - roughly a 3x edge to C.

workbench

Test cases

categoryinputanswer
main n = 4 ['1487 4817 8147', '2969 6299 9629']
extra n = 5 ['11483 14813 18143', '11497 41719 71941', '12713 13217 13721', '12739 17239 21739', '12757 17257 21757', '12799 17299 21799', '14821 48121 81421', '14831 31481 48131', '18503 51803 85103', '19543 35491 51439', '20161 20611 21061', '20353 25303 30253', '20359 25309 30259', '20747 24077 27407', '23887 28387 32887', '25087 52807 80527', '25793 59273 92753', '25981 59281 92581', '29669 62969 96269', '31489 34819 38149', '31489 39841 48193', '32969 63299 93629', '34961 39461 43961', '35407 40357 45307', '35671 53617 71563', '37561 51637 65713', '49547 54497 59447', '55603 56053 56503', '60373 63703 67033', '60757 65707 70657', '61487 64817 68147', '62597 65927 69257', '62773 67723 72673', '63499 63949 64399', '67829 68279 68729', '68713 78163 87613', '71947 74719 77491', '73589 78593 83597', '76717 77167 77617', '76819 81769 86719', '89387 93887 98387', '92381 92831 93281']

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0049_s0 main ['1487 4817 8147', '2969 6299 9629'] correct 0.002668 (63) 0.010760 (63)
p0049_s0 extra ['11483 14813 18143', '11497 41719 71941', '12713 13217 13721', '12739 17239 21739', '12757 17257 21757', '12799 17299 21799', '14821 48121 81421', '14831 31481 48131', '18503 51803 85103', '19543 35491 51439', '20161 20611 21061', '20353 25303 30253', '20359 25309 30259', '20747 24077 27407', '23887 28387 32887', '25087 52807 80527', '25793 59273 92753', '25981 59281 92581', '29669 62969 96269', '31489 34819 38149', '31489 39841 48193', '32969 63299 93629', '34961 39461 43961', '35407 40357 45307', '35671 53617 71563', '37561 51637 65713', '49547 54497 59447', '55603 56053 56503', '60373 63703 67033', '60757 65707 70657', '61487 64817 68147', '62597 65927 69257', '62773 67723 72673', '63499 63949 64399', '67829 68279 68729', '68713 78163 87613', '71947 74719 77491', '73589 78593 83597', '76717 77167 77617', '76819 81769 86719', '89387 93887 98387', '92381 92831 93281'] correct 0.164098 (63) 0.465499 (63)

Files

Tags & blogs

Blogs