1414How many reversible numbers are there below one-billion (10^9)?
1515"""
1616
17- EVEN_DIGITS = [0 , 2 , 4 , 6 , 8 ]
18- ODD_DIGITS = [1 , 3 , 5 , 7 , 9 ]
1917
20-
21- def slow_reversible_numbers (
22- remaining_length : int , remainder : int , digits : list [int ], length : int
23- ) -> int :
24- """
25- Count the number of reversible numbers of given length.
26- Iterate over possible digits considering parity of current sum remainder.
27- >>> slow_reversible_numbers(1, 0, [0], 1)
28- 0
29- >>> slow_reversible_numbers(2, 0, [0] * 2, 2)
30- 20
31- >>> slow_reversible_numbers(3, 0, [0] * 3, 3)
32- 100
18+ def solution (max_power : int = 9 ) -> int :
3319 """
34- if remaining_length == 0 :
35- if digits [0 ] == 0 or digits [- 1 ] == 0 :
36- return 0
20+ This solution counts reversible numbers below 10^max_power
21+ using mathematical patterns instead of brute force.
3722
38- for i in range ( length // 2 - 1 , - 1 , - 1 ) :
39- remainder += digits [ i ] + digits [ length - i - 1 ]
23+ A reversible number is a number where :
24+ n + reverse(n)
4025
41- if remainder % 2 == 0 :
42- return 0
26+ contains only odd digits.
4327
44- remainder //= 10
28+ Example:
29+ 36 + 63 = 99
30+ 409 + 904 = 1313
4531
46- return 1
32+ Instead of checking every number one by one, we observe
33+ some repeating patterns based on the number of digits.
4734
48- if remaining_length == 1 :
49- if remainder % 2 == 0 :
50- return 0
35+ --------------------------------------------------------
36+ Main Observations
37+ --------------------------------------------------------
5138
52- result = 0
53- for digit in range (10 ):
54- digits [length // 2 ] = digit
55- result += slow_reversible_numbers (
56- 0 , (remainder + 2 * digit ) // 10 , digits , length
57- )
58- return result
39+ 1. Numbers with length = 1 (mod 4)
40+ ----------------------------------
41+ These lengths never work because the carry pattern becomes
42+ inconsistent while adding the number and its reverse.
5943
60- result = 0
61- for digit1 in range (10 ):
62- digits [(length + remaining_length ) // 2 - 1 ] = digit1
63-
64- if (remainder + digit1 ) % 2 == 0 :
65- other_parity_digits = ODD_DIGITS
66- else :
67- other_parity_digits = EVEN_DIGITS
68-
69- for digit2 in other_parity_digits :
70- digits [(length - remaining_length ) // 2 ] = digit2
71- result += slow_reversible_numbers (
72- remaining_length - 2 ,
73- (remainder + digit1 + digit2 ) // 10 ,
74- digits ,
75- length ,
76- )
77- return result
44+ Examples:
45+ 1 digit, 5 digits, 9 digits ...
7846
47+ Count = 0
7948
80- def slow_solution (max_power : int = 9 ) -> int :
81- """
82- To evaluate the solution, use solution()
83- >>> slow_solution(3)
84- 120
85- >>> slow_solution(6)
86- 18720
87- >>> slow_solution(7)
88- 68720
89- """
90- result = 0
91- for length in range (1 , max_power + 1 ):
92- result += slow_reversible_numbers (length , 0 , [0 ] * length , length )
93- return result
9449
50+ 2. Even length numbers
51+ -----------------------
52+ For numbers with even digits (2, 4, 6, 8 ...):
9553
96- def reversible_numbers (
97- remaining_length : int , remainder : int , digits : list [int ], length : int
98- ) -> int :
99- """
100- Count the number of reversible numbers of given length.
101- Iterate over possible digits considering parity of current sum remainder.
102- >>> reversible_numbers(1, 0, [0], 1)
103- 0
104- >>> reversible_numbers(2, 0, [0] * 2, 2)
105- 20
106- >>> reversible_numbers(3, 0, [0] * 3, 3)
107- 100
108- """
109- # There exist no reversible 1, 5, 9, 13 (ie. 4k+1) digit numbers
110- if (length - 1 ) % 4 == 0 :
111- return 0
54+ - Each pair of digits must produce an odd sum.
55+ - One digit in the pair must be even and the other odd.
56+ - The carry pattern stays consistent.
11257
113- return slow_reversible_numbers (remaining_length , remainder , digits , length )
58+ Counting possibilities:
59+ - First pair has 20 valid combinations
60+ (leading digit cannot be zero)
11461
62+ - Every inner pair has 30 valid combinations
11563
116- def solution (max_power : int = 9 ) -> int :
117- """
118- To evaluate the solution, use solution()
119- >>> solution(3)
120- 120
121- >>> solution(6)
122- 18720
123- >>> solution(7)
124- 68720
125- """
64+ Formula:
65+ 20 * 30^(k-1)
66+
67+ where:
68+ length = 2k
69+
70+ Examples:
71+ 2 digits -> 20
72+ 4 digits -> 600
73+ 6 digits -> 18000
74+ 8 digits -> 540000
75+
76+
77+ 3. Length = 3 (mod 4)
78+ ----------------------
79+ These are lengths like:
80+ 3, 7, 11 ...
81+
82+ Here the middle digit creates a special carry cycle,
83+ which only works for lengths of the form:
84+
85+ 4j + 3
86+
87+ Formula:
88+ 100 * 500^j
89+
90+ Examples:
91+ 3 digits -> 100
92+ 7 digits -> 50000
93+
94+
95+ --------------------------------------------------------
96+ Complexity
97+ --------------------------------------------------------
98+
99+ Time Complexity:
100+ O(max_power)
101+
102+ Space Complexity:
103+ O(1)
104+
105+ The algorithm is extremely fast because it only loops
106+ through digit lengths instead of checking every number.
107+ """
126108 result = 0
127109 for length in range (1 , max_power + 1 ):
128- result += reversible_numbers (length , 0 , [0 ] * length , length )
110+ if length % 2 == 0 :
111+ # Even length 2k -> 20 x 30^(k-1)
112+ k = length // 2
113+ result += 20 * (30 ** (k - 1 ))
114+ elif length % 4 == 3 :
115+ # Odd length 4j+3 -> 100 x 500^j
116+ j = (length - 3 ) // 4
117+ result += 100 * (500 ** j )
118+ # Lengths == 1 (mod 4) contribute 0 and are intentionally skipped.
119+
129120 return result
130121
131122
132123def benchmark () -> None :
133124 """
134125 Benchmarks
135126 """
136- # Running performance benchmarks...
137- # slow_solution : 292.9300301000003
138- # solution : 54.90970860000016
139-
140127 from timeit import timeit
141128
142129 print ("Running performance benchmarks..." )
143-
144- print (f"slow_solution : { timeit ('slow_solution()' , globals = globals (), number = 10 )} " )
145- print (f"solution : { timeit ('solution()' , globals = globals (), number = 10 )} " )
130+ print (f"solution : { timeit ('solution()' , globals = globals (), number = 10_000 )} " )
146131
147132
148133if __name__ == "__main__" :
149134 print (f"Solution : { solution ()} " )
150- benchmark ()
151-
152- # for i in range(1, 15):
153- # print(f"{i}. {reversible_numbers(i, 0, [0]*i, i)}")
135+ benchmark ()
0 commit comments