Problem 52 — Permuted Multiples

solved Fri, 6 Dec 2019, 12:24 · level 0 · difficulty 2% projecteuler.net github

It can be seen that the number, $125874$, and its double, $251748$, contain exactly the same digits, but in a different order.

Find the smallest positive integer, $x$, such that $2x$, $3x$, $4x$, $5x$, and $6x$, contain the same digits.


Problem Analysis

The mathematical core of this problem is the notion of a digit multiset: two integers are permutations of one another exactly when they contain the same decimal digits with the same multiplicities, irrespective of order. The challenge is to test this relation cheaply. My tool is a canonical form - a single, unique representative chosen for each class of digit-permutations. The obvious choice is the string of the number's decimal digits sorted into ascending order. I call this a fingerprint. Two numbers are digit-permutations of each other if and only if their fingerprints are identical, so the awkward question "do these share the same digits?" collapses into a plain string equality test.

The second observation bounds the search and proves that brute force is safe. If x has d decimal digits, any permutation of x must also have exactly d digits. Therefore x, 2x, ..., 6x must all have the same digit count. Since 6x is the largest, this forces x into the lower sixth of its digit-length band (roughly x < 10^d / 6); otherwise 6x would carry into the next band and gain a digit. Consequently the answer is never far from the start of any band, and a simple upward linear scan finds 142857 after about 140,000 iterations. The total work is O(N * M * D) where N is the answer, M the number of multiples (at most 6), and D the digit count (at most 7) - trivially small.

Solution Approach

Linear Scan with Sorted-Digit Fingerprinting (Python and C)

Both files implement the same algorithm. I iterate over candidates x = 1, 2, 3, .... For each x I compute the fingerprint of every required multiple and check whether they all agree with the fingerprint of x itself. The first x for which they all coincide is the answer.

In Python I express the test with a set comprehension: {"".join(sorted(str(i * multiple))) for multiple in multiples_range}. Building a set collapses duplicate fingerprints, so if every multiple shares the same digits the set has exactly one element. The condition len(...) == 1 therefore reads as "all fingerprints are equal." I bound the loop at sys.maxsize // multiples as a conceptual overflow guard, though Python integers are unbounded in practice.

In C I implement fingerprinting by hand. The sorted_digits function extracts digits by repeatedly taking n % 10 and dividing by 10, writing them into a buffer, then calls qsort to sort that buffer and null-terminates it. I compare fingerprints with strcmp. Crucially I use an early exit: in the inner loop over multiples 2..M, the moment a multiple fails to match I set all_match = 0 and break. Because the vast majority of candidates fail at the very first multiple (m = 2), most iterations do only a single comparison.

Both versions take the number of multiples as a parameter (a command-line argument in C, a positional argument in Python), so the same code verifies the simpler 2-multiple case (answer 125874, given in the statement) as well as the full 6-multiple case.

workbench

Test cases

categoryinputanswer
dev multiples = 2 125874
dev multiples = 3 142857
dev multiples = 4 142857
dev multiples = 5 142857
main multiples = 6 142857

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0052_s0 dev 125874 correct 0.020787 (63) 0.105659 (63)
p0052_s0 dev 142857 correct 0.025155 (63) 0.181627 (63)
p0052_s0 dev 142857 correct 0.022833 (63) 0.239657 (63)
p0052_s0 dev 142857 correct 0.022224 (63) 0.307172 (63)
p0052_s0 main 142857 correct 0.022514 (63) 0.361684 (63)

Files

Tags & blogs

Blogs