Problem 36 — Double-base Palindromes

solved Wed, 13 Nov 2019, 11:27 · level 0 · difficulty 1% projecteuler.net github

The decimal number, $585 = 1001001001_2$ (binary), is palindromic in both bases.

Find the sum of all numbers, less than one million, which are palindromic in base $10$ and base $2$.

(Please note that the palindromic number, in either base, may not include leading zeros.)


Problem Analysis

I am asked to sum every positive integer below one million that is a palindrome in base 10 and simultaneously a palindrome in base 2. A palindrome is a finite sequence of symbols that reads the same forwards and backwards; here the symbols are the decimal digits in one case and the binary bits in the other. The "no leading zeros" rule means the most-significant symbol must be nonzero in each base. For base 2 this is automatic: the highest bit of any positive integer is, by definition, a 1. For base 10 I simply have to avoid placing a zero in the leading position when I construct candidates.

The naive method - iterate over all N integers below the limit and test each in both bases - runs in O(N) and wastes effort, because decimal palindromes are extremely sparse among the integers. The observation that makes the problem tractable is that a decimal palindrome is completely determined by its left half. A k-digit prefix mirrored onto itself yields exactly one even-length palindrome of 2k digits, and inserting any of ten interior digits yields ten odd-length palindromes of 2k+1 digits. The number of distinct halves is on the order of the square root of N, so this generate-and-test strategy reduces the candidate count from O(N) to O(sqrt(N)). Each candidate is then checked for binary palindromicity in O(log N) time by inspecting its bits, so the overall cost is O(sqrt(N) log N) - which is why even the 9-digit case finishes in a fraction of a second.

Solution Approach

Decimal Palindrome Generation with a Binary Filter (s0 - Python and C)

Both files implement the same algorithm; I describe it once and note the language differences afterward.

Step 1 - single-digit seeds. The digits 1 through 9 are palindromes in every base by definition, so I emit each one (subject to the binary test). I handle them separately because the mirroring construction below would turn the half "1" into the two-digit string "11" - it can never reproduce the single-digit numbers themselves.

Step 2 - enumerate halves and mirror. The half loop runs over integers from 1 up to 10^(max_digits // 2) - 1. For each value I take its decimal string, reverse it, and concatenate the two to form an even-length palindrome. This construction is exact and exhaustive: every even-length decimal palindrome with no leading zero is produced exactly once, since the half itself begins with a nonzero digit.

Step 3 - odd-length palindromes. When inserting a middle digit still fits the budget - guarded by 2 * num_digits < max_digits - I loop over middle digits 0 through 9 and form half + mid + reverse(half). A middle digit of 0 is legal here because it is an interior symbol, not the leading one.

Step 4 - binary palindrome test. Each candidate is tested for binary palindromicity. In Python this is the one-liner number == int(bin(number)[2:][::-1], 2): strip the 0b prefix, reverse the bit string, parse it back, and compare. In C I extract bits one at a time into a small char array (least-significant bit first) and check bits[i] == bits[len-1-i]. A nice consequence falls out of the bit structure: an even number has its lowest bit 0, but a binary palindrome must have its highest bit 1, so even numbers can never pass - the test discards them implicitly at no extra cost.

Language differences. The Python version isolates generation in a generator function that yields candidates lazily, with the filter applied in a generator expression inside sum(). The C version performs the same steps inline, building strings with snprintf and converting with atoll, and uses a small int_pow10 helper for the loop bound. Both produce identical answers across every test case.

workbench

Test cases

categoryinputanswer
dev max_digits = 1 25
dev max_digits = 2 157
dev max_digits = 3 1772
dev max_digits = 4 18228
main max_digits = 6 872187
extra max_digits = 9 2609044274

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0036_s0 dev 25 correct 0.000001 (63) 0.000004 (63)
p0036_s0 dev 157 correct 0.000001 (63) 0.000008 (63)
p0036_s0 dev 1772 correct 0.000008 (63) 0.000060 (63)
p0036_s0 dev 18228 correct 0.000024 (63) 0.000086 (63)
p0036_s0 main 872187 correct 0.000163 (63) 0.000990 (63)
p0036_s0 extra 2609044274 correct 0.010053 (63) 0.052710 (63)

Files

Tags & blogs

Blogs