Problem 55 — Lychrel Numbers

solved Thu, 12 Dec 2019, 12:29 · level 1 · difficulty 4% projecteuler.net github

If we take $47$, reverse and add, $47 + 74 = 121$, which is palindromic.

Not all numbers produce palindromes so quickly. For example,

$$\begin{align} 349 + 943 &= 1292\\ 1292 + 2921 &= 4213\\ 4213 + 3124 &= 7337 \end{align}$$

That is, $349$ took three iterations to arrive at a palindrome.

Although no one has proved it yet, it is thought that some numbers, like $196$, never produce a palindrome. A number that never forms a palindrome through the reverse and add process is called a Lychrel number. Due to the theoretical nature of these numbers, and for the purpose of this problem, we shall assume that a number is Lychrel until proven otherwise. In addition you are given that for every number below ten-thousand, it will either (i) become a palindrome in less than fifty iterations, or, (ii) no one, with all the computing power that exists, has managed so far to map it to a palindrome. In fact, $10677$ is the first number to be shown to require over fifty iterations before producing a palindrome: $4668731596684224866951378664$ ($53$ iterations, $28$-digits).

Surprisingly, there are palindromic numbers that are themselves Lychrel numbers; the first example is $4994$.

How many Lychrel numbers are there below ten-thousand?

NOTE: Wording was modified slightly on 24 April 2007 to emphasise the theoretical nature of Lychrel numbers.


Problem Analysis

This problem revolves around a simple iterative transformation: take a number, reverse its decimal digits, add the two together, and check whether the result is a palindromic number - a number that reads the same forwards and backwards, like 121 or 7337. If the sum is not a palindrome, we repeat the reverse-and-add on the new value. A Lychrel number is one that, conjecturally, never reaches a palindrome by this process. The mathematical subtlety is that no number has ever been *proven* to be Lychrel; in base 10 the property is, as far as anyone knows, undecidable. What rescues the problem is the bounded budget the statement supplies: every number below ten thousand either reaches a palindrome within fifty iterations, or has resisted all computation ever attempted. That lets me redefine "Lychrel" operationally as "did not become a palindrome within 50 reverse-and-add steps," turning an open mathematical question into a finite, well-defined search.

The one engineering wrinkle is the magnitude of the intermediate values. Each reverse-and-add roughly doubles the value and can add a digit, so after dozens of steps the numbers overflow 64-bit integers - the statement itself cites a 28-digit result for 10677. Any correct solution therefore needs arbitrary-precision arithmetic. The total cost is O(N * I * D), where N = 10,000 is the search limit, I = 50 is the iteration cap, and D is the digit-count of the largest intermediate (comfortably under 30). Because D stays tiny, the computation is effectively linear in N and finishes in milliseconds.

Solution Approach

Bounded Iterative Reverse-and-Add with Big-Integer Arithmetic (Python and C)

Both solutions share one algorithm. For each candidate i from 1 to the limit I call is_lychrel, which performs up to max_iterations rounds of reverse-and-add. In each round I first apply the operation, then test the result for palindromicity. The ordering matters: a starting value that is itself a palindrome - 4994 is the smallest example - can still be a Lychrel number, so I must judge the *result* of the step, not the seed. If any step yields a palindrome, the number is not Lychrel and I return false; if the loop exhausts its budget, I return true.

In Python the code is short because integers are arbitrary-precision natively. I reverse the digits by converting to a string, reversing it with the slice [::-1], and parsing back with int(...). The palindrome test reuses the same idea: a number is a palindrome exactly when str(number) == str(number)[::-1]. The counting in solve is a single sum over a generator of boolean results.

In C there is no built-in big integer, so I defined a fixed-width BigNum: an array of decimal digits stored little-endian (least significant digit at index 0) with an explicit length. I chose base 10 rather than a larger base on purpose - the palindrome test operates on decimal digits, so storing in base 10 means I never convert between an arithmetic base and a display base. bignum_add_reverse computes a + reverse(a) in a single pass: for index i it adds a.digits[i] to a.digits[n-1-i] with carry, because the little-endian position i is exactly the big-endian position n-1-i of the same number. This avoids materialising a separate reversed copy. A 128-digit buffer is far more than 50 iterations starting below 10,000 will ever need. The palindrome check just compares symmetric digit pairs. Both implementations agree on the answer, 249.

workbench

Test cases

categoryinputanswer
main max_iterations = 50 max_limit = 10000 249

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0055_s0 main 249 correct 0.001031 (63) 0.015639 (63)

Files

Tags & blogs

Blogs