From caffc9f46f502c0e4d70e9974c0023a4e569cc66 Mon Sep 17 00:00:00 2001 From: Dinesh69069 Date: Sun, 17 May 2026 12:20:49 +0530 Subject: [PATCH 1/2] perf: optimize problem 145 to O(1) time complexity --- project_euler/problem_145/sol1.py | 192 ++++++++++++++---------------- 1 file changed, 87 insertions(+), 105 deletions(-) diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index 583bb03a0a90..7a448f60edc4 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -14,118 +14,109 @@ How many reversible numbers are there below one-billion (10^9)? """ -EVEN_DIGITS = [0, 2, 4, 6, 8] -ODD_DIGITS = [1, 3, 5, 7, 9] - -def slow_reversible_numbers( - remaining_length: int, remainder: int, digits: list[int], length: int -) -> int: - """ - Count the number of reversible numbers of given length. - Iterate over possible digits considering parity of current sum remainder. - >>> slow_reversible_numbers(1, 0, [0], 1) - 0 - >>> slow_reversible_numbers(2, 0, [0] * 2, 2) - 20 - >>> slow_reversible_numbers(3, 0, [0] * 3, 3) - 100 +def solution(max_power: int = 9) -> int: """ - if remaining_length == 0: - if digits[0] == 0 or digits[-1] == 0: - return 0 +This solution counts reversible numbers below 10^max_power +using mathematical patterns instead of brute force. - for i in range(length // 2 - 1, -1, -1): - remainder += digits[i] + digits[length - i - 1] +A reversible number is a number where: + n + reverse(n) - if remainder % 2 == 0: - return 0 +contains only odd digits. - remainder //= 10 +Example: + 36 + 63 = 99 + 409 + 904 = 1313 - return 1 +Instead of checking every number one by one, we observe +some repeating patterns based on the number of digits. - if remaining_length == 1: - if remainder % 2 == 0: - return 0 +-------------------------------------------------------- +Main Observations +-------------------------------------------------------- - result = 0 - for digit in range(10): - digits[length // 2] = digit - result += slow_reversible_numbers( - 0, (remainder + 2 * digit) // 10, digits, length - ) - return result +1. Numbers with length = 1 (mod 4) +---------------------------------- +These lengths never work because the carry pattern becomes +inconsistent while adding the number and its reverse. - result = 0 - for digit1 in range(10): - digits[(length + remaining_length) // 2 - 1] = digit1 - - if (remainder + digit1) % 2 == 0: - other_parity_digits = ODD_DIGITS - else: - other_parity_digits = EVEN_DIGITS - - for digit2 in other_parity_digits: - digits[(length - remaining_length) // 2] = digit2 - result += slow_reversible_numbers( - remaining_length - 2, - (remainder + digit1 + digit2) // 10, - digits, - length, - ) - return result +Examples: + 1 digit, 5 digits, 9 digits ... +Count = 0 -def slow_solution(max_power: int = 9) -> int: - """ - To evaluate the solution, use solution() - >>> slow_solution(3) - 120 - >>> slow_solution(6) - 18720 - >>> slow_solution(7) - 68720 - """ - result = 0 - for length in range(1, max_power + 1): - result += slow_reversible_numbers(length, 0, [0] * length, length) - return result +2. Even length numbers +----------------------- +For numbers with even digits (2, 4, 6, 8 ...): -def reversible_numbers( - remaining_length: int, remainder: int, digits: list[int], length: int -) -> int: - """ - Count the number of reversible numbers of given length. - Iterate over possible digits considering parity of current sum remainder. - >>> reversible_numbers(1, 0, [0], 1) - 0 - >>> reversible_numbers(2, 0, [0] * 2, 2) - 20 - >>> reversible_numbers(3, 0, [0] * 3, 3) - 100 - """ - # There exist no reversible 1, 5, 9, 13 (ie. 4k+1) digit numbers - if (length - 1) % 4 == 0: - return 0 +- Each pair of digits must produce an odd sum. +- One digit in the pair must be even and the other odd. +- The carry pattern stays consistent. - return slow_reversible_numbers(remaining_length, remainder, digits, length) +Counting possibilities: + - First pair has 20 valid combinations + (leading digit cannot be zero) + - Every inner pair has 30 valid combinations -def solution(max_power: int = 9) -> int: - """ - To evaluate the solution, use solution() - >>> solution(3) - 120 - >>> solution(6) - 18720 - >>> solution(7) - 68720 - """ +Formula: + 20 * 30^(k-1) + +where: + length = 2k + +Examples: + 2 digits -> 20 + 4 digits -> 600 + 6 digits -> 18000 + 8 digits -> 540000 + + +3. Length = 3 (mod 4) +---------------------- +These are lengths like: + 3, 7, 11 ... + +Here the middle digit creates a special carry cycle, +which only works for lengths of the form: + + 4j + 3 + +Formula: + 100 * 500^j + +Examples: + 3 digits -> 100 + 7 digits -> 50000 + + +-------------------------------------------------------- +Complexity +-------------------------------------------------------- + +Time Complexity: + O(max_power) + +Space Complexity: + O(1) + +The algorithm is extremely fast because it only loops +through digit lengths instead of checking every number. +""" result = 0 for length in range(1, max_power + 1): - result += reversible_numbers(length, 0, [0] * length, length) + if length % 2 == 0: + # Even length 2k -> 20 x 30^(k-1) + k = length // 2 + result += 20 * (30 ** (k - 1)) + elif length % 4 == 3: + # Odd length 4j+3 -> 100 x 500^j + j = (length - 3) // 4 + result += 100 * (500 ** j) + # Lengths == 1 (mod 4) contribute 0 and are intentionally skipped. + return result @@ -133,21 +124,12 @@ def benchmark() -> None: """ Benchmarks """ - # Running performance benchmarks... - # slow_solution : 292.9300301000003 - # solution : 54.90970860000016 - from timeit import timeit print("Running performance benchmarks...") - - print(f"slow_solution : {timeit('slow_solution()', globals=globals(), number=10)}") - print(f"solution : {timeit('solution()', globals=globals(), number=10)}") + print(f"solution : {timeit('solution()', globals=globals(), number=10_000)}") if __name__ == "__main__": print(f"Solution : {solution()}") - benchmark() - - # for i in range(1, 15): - # print(f"{i}. {reversible_numbers(i, 0, [0]*i, i)}") + benchmark() \ No newline at end of file From f4c606f3eeb844a1b5395f5ea4e21da74c41e3fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 17 May 2026 06:59:31 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- project_euler/problem_145/sol1.py | 126 +++++++++++++++--------------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/project_euler/problem_145/sol1.py b/project_euler/problem_145/sol1.py index 7a448f60edc4..4230d41f31da 100644 --- a/project_euler/problem_145/sol1.py +++ b/project_euler/problem_145/sol1.py @@ -17,94 +17,94 @@ def solution(max_power: int = 9) -> int: """ -This solution counts reversible numbers below 10^max_power -using mathematical patterns instead of brute force. + This solution counts reversible numbers below 10^max_power + using mathematical patterns instead of brute force. -A reversible number is a number where: - n + reverse(n) + A reversible number is a number where: + n + reverse(n) -contains only odd digits. + contains only odd digits. -Example: - 36 + 63 = 99 - 409 + 904 = 1313 + Example: + 36 + 63 = 99 + 409 + 904 = 1313 -Instead of checking every number one by one, we observe -some repeating patterns based on the number of digits. + Instead of checking every number one by one, we observe + some repeating patterns based on the number of digits. --------------------------------------------------------- -Main Observations --------------------------------------------------------- + -------------------------------------------------------- + Main Observations + -------------------------------------------------------- -1. Numbers with length = 1 (mod 4) ----------------------------------- -These lengths never work because the carry pattern becomes -inconsistent while adding the number and its reverse. + 1. Numbers with length = 1 (mod 4) + ---------------------------------- + These lengths never work because the carry pattern becomes + inconsistent while adding the number and its reverse. -Examples: - 1 digit, 5 digits, 9 digits ... + Examples: + 1 digit, 5 digits, 9 digits ... -Count = 0 + Count = 0 -2. Even length numbers ------------------------ -For numbers with even digits (2, 4, 6, 8 ...): + 2. Even length numbers + ----------------------- + For numbers with even digits (2, 4, 6, 8 ...): -- Each pair of digits must produce an odd sum. -- One digit in the pair must be even and the other odd. -- The carry pattern stays consistent. + - Each pair of digits must produce an odd sum. + - One digit in the pair must be even and the other odd. + - The carry pattern stays consistent. -Counting possibilities: - - First pair has 20 valid combinations - (leading digit cannot be zero) + Counting possibilities: + - First pair has 20 valid combinations + (leading digit cannot be zero) - - Every inner pair has 30 valid combinations + - Every inner pair has 30 valid combinations -Formula: - 20 * 30^(k-1) + Formula: + 20 * 30^(k-1) -where: - length = 2k + where: + length = 2k -Examples: - 2 digits -> 20 - 4 digits -> 600 - 6 digits -> 18000 - 8 digits -> 540000 + Examples: + 2 digits -> 20 + 4 digits -> 600 + 6 digits -> 18000 + 8 digits -> 540000 -3. Length = 3 (mod 4) ----------------------- -These are lengths like: - 3, 7, 11 ... + 3. Length = 3 (mod 4) + ---------------------- + These are lengths like: + 3, 7, 11 ... -Here the middle digit creates a special carry cycle, -which only works for lengths of the form: + Here the middle digit creates a special carry cycle, + which only works for lengths of the form: - 4j + 3 + 4j + 3 -Formula: - 100 * 500^j + Formula: + 100 * 500^j -Examples: - 3 digits -> 100 - 7 digits -> 50000 + Examples: + 3 digits -> 100 + 7 digits -> 50000 --------------------------------------------------------- -Complexity --------------------------------------------------------- + -------------------------------------------------------- + Complexity + -------------------------------------------------------- -Time Complexity: - O(max_power) + Time Complexity: + O(max_power) -Space Complexity: - O(1) + Space Complexity: + O(1) -The algorithm is extremely fast because it only loops -through digit lengths instead of checking every number. -""" + The algorithm is extremely fast because it only loops + through digit lengths instead of checking every number. + """ result = 0 for length in range(1, max_power + 1): if length % 2 == 0: @@ -114,7 +114,7 @@ def solution(max_power: int = 9) -> int: elif length % 4 == 3: # Odd length 4j+3 -> 100 x 500^j j = (length - 3) // 4 - result += 100 * (500 ** j) + result += 100 * (500**j) # Lengths == 1 (mod 4) contribute 0 and are intentionally skipped. return result @@ -132,4 +132,4 @@ def benchmark() -> None: if __name__ == "__main__": print(f"Solution : {solution()}") - benchmark() \ No newline at end of file + benchmark()