diff --git a/solutions/0034/euler_34.py b/solutions/0034/euler_34.py new file mode 100644 index 0000000..1481dd7 --- /dev/null +++ b/solutions/0034/euler_34.py @@ -0,0 +1,67 @@ +""" +145 is a curious number, as 1! + 4! + 5! = 1 + 24 + 120 = 145. + +Find the sum of all numbers which are equal to the sum of the factorial of their digits. + +Note: As 1!=1 and 2!=2 are not sums they are not included. +""" + +import time +from functools import lru_cache + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} time: {end_time - start_time:.6f} seconds") + return result + + return wrapper + + +@lru_cache(maxsize=None) +def factorial(n: int) -> int: + if n == 0: + return 1 + elif n == 1: + return 1 + elif n == 2: + return 2 + elif n == 3: + return 6 + elif n == 4: + return 24 + elif n == 5: + return 120 + elif n == 6: + return 720 + elif n == 7: + return 5040 + elif n == 8: + return 40320 + elif n == 9: + return 362880 + elif n == 10: + return 3628800 + else: + return n * factorial(n - 1) + + +def sum_digits(n: int) -> int: + tmp = str(n) + return sum(factorial(int(digit)) for digit in tmp) + + +@timer +def main() -> None: + res = list() + for i in range(12, 9999999): + if sum_digits(i) == i: + res.append(i) + print(sum(res)) + + +if __name__ == "__main__": + main() diff --git a/solutions/0034/readme.md b/solutions/0034/readme.md new file mode 100644 index 0000000..a3a88fa --- /dev/null +++ b/solutions/0034/readme.md @@ -0,0 +1,8 @@ +参考 [Factorion](https://mathworld.wolfram.com/Factorion.html) + +--- + +本来想的是,是否可以确定满足这个规则的数列上限。口算估计是999999,然后就是暴力计算了。 +看到结果,则比较让我惊讶,这样的数只有两个:145和40585。 + +再加上看别人的思考,从我个人来说,可能不是所有问题都适合这个思路? diff --git a/solutions/0035.CircularPrimes/euler_35.py b/solutions/0035.CircularPrimes/euler_35.py new file mode 100644 index 0000000..96fd343 --- /dev/null +++ b/solutions/0035.CircularPrimes/euler_35.py @@ -0,0 +1,206 @@ +""" +The number, 179, is called a circular prime because all rotations of the digits: 179, 791, and 917, +are themselves prime. + +There are thirteen such primes below 100 : 2,3,5,7,11,13,17,31,37,71,73,79, and 97. + +How many circular primes are there below one million? +""" + +import random +import time +from itertools import permutations +from sqlite3.dbapi2 import Time +from typing import Union + +# 预计算的小素数集合,用于快速排除 +_SMALL_PRIMES = frozenset((2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37)) + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} time: {end_time - start_time:.6f} seconds") + return result + + return wrapper + + +def sieve_best(n: int) -> list[int]: + """返回 [2..n) 内所有素数""" + if n <= 2: + return [] + size = (n - 1) // 2 + comp = bytearray(size) + limit = int(n**0.5) // 2 + for i in range(1, limit + 1): + if not comp[i]: + p = 2 * i + 1 + s = (p * p - 1) // 2 + comp[s::p] = b"\1" * ((size - s - 1) // p + 1) + res = [2, *(2 * i + 1 for i, v in enumerate(comp) if not v)] + res.sort() + return [i for i in res if i != 1] + + +def is_circular(n: int, primes: list[int]) -> bool: + if n < 2: + return False + if n in [2, 3, 5, 7, 11]: + return True + digits = str(n) + allnum = [digits[i:] + digits[:i] for i in range(len(digits))] + for num in allnum: + if int(num) not in primes: + return False + return True + + +def is_probable_prime(n: Union[int, str], k: int = 20) -> bool: + """ + Miller-Rabin素性检测算法 + + 参数: + n: 待检测的整数(支持int或数字字符串) + k: 测试次数,误差率约为4^(-k),默认20次(误差率约9e-13) + + 返回: + bool: 如果n很可能是素数返回True,否则返回False + """ + # 支持字符串输入 + if isinstance(n, str): + n = int(n) + + # 基本检查 + if n < 2: + return False + if n in _SMALL_PRIMES: + return True + + # 检查小素数的倍数 + for p in _SMALL_PRIMES: + if n % p == 0: + return False + + # 特殊情况:偶数(已排除2) + if n % 2 == 0: + return False + + # 将 n-1 写成 2^s * d 的形式 + d = n - 1 + s = 0 + while d % 2 == 0: + d //= 2 + s += 1 + + # 边界:如果 n-1 可以直接作为基数,避免随机数生成问题 + if n < 4: + return n in (2, 3) + + # Miller-Rabin测试 + for _ in range(k): + # 生成随机基数,范围 [2, n-2] + a = random.randrange(2, n - 1) + + # 计算 x = a^d mod n + x = pow(a, d, n) + + # 如果 x == 1 或 x == n-1,通过本轮测试 + if x == 1 or x == n - 1: + continue + + # 重复平方检查 + for _ in range(s - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + # 所有平方后都没有得到 n-1,n是合数 + return False + + return True + + +# 优化的确定素数版本(适用于小整数) +def is_prime_small(n: int) -> bool: + """确定性素数检测,适用于n < 2^64""" + if n < 2: + return False + if n % 2 == 0: + return n == 2 + if n % 3 == 0: + return n == 3 + + # 检查小素数 + for p in _SMALL_PRIMES: + if n % p == 0: + return n == p + + # 6k±1 优化检查 + i = 5 + w = 2 + while i * i <= n: + if n % i == 0: + return False + i += w + w = 6 - w # 在2和4之间切换:5,7,11,13,17,19... + + return True + + +# 智能选择函数 +def is_prime(n: Union[int, str], k: int = 20) -> bool: + """ + 智能素数检测:对小整数使用确定性算法,对大整数使用Miller-Rabin + + 参数: + n: 待检测的整数 + k: Miller-Rabin测试次数(仅对大整数有效) + + 返回: + bool: 如果是素数返回True + """ + if isinstance(n, str): + n = int(n) + + # 小整数使用确定性算法 + if n < 2**64: + return is_prime_small(n) + + # 大整数使用Miller-Rabin + return is_probable_prime(n, k) + + +@timer +def main_quick(max: int) -> None: + res = set() + for i in range(2, max): + if is_prime(i): + if i in res: + continue + else: + s = str(i) + allnums = [s[i:] + s[:i] for i in range(len(s))] + flag = 0 + for j in allnums: + num = int("".join(j)) + if is_prime(num): + flag += 1 + if flag == len(allnums): + res.update(allnums) + print(len(res)) + + +@timer +def main(n: int) -> None: + primes = sieve_best(n) + circular_primes = [p for p in primes if is_circular(p, primes)] + print(len(circular_primes)) + + +if __name__ == "__main__": + num = 1000000 + main(num) + main_quick(num) diff --git a/solutions/0035.CircularPrimes/euler_35_better.py b/solutions/0035.CircularPrimes/euler_35_better.py new file mode 100644 index 0000000..6744499 --- /dev/null +++ b/solutions/0035.CircularPrimes/euler_35_better.py @@ -0,0 +1,80 @@ +import time + + +def timer(func): + def wrapper(*args, **kwargs): + start_time = time.time() + result = func(*args, **kwargs) + end_time = time.time() + print(f"{func.__name__} took {end_time - start_time:.6f} seconds") + return result + + return wrapper + + +# ---------- 1. PRIMES ---------- +MAX = 1_000_000 +is_p = bytearray(b"\1") * MAX # 1 表示素数,0 表示合数(与原来相反,方便理解) +is_p[0] = is_p[1] = 0 # 0和1不是素数 + +# 优化:使用标准埃拉托斯特尼筛法 +for p in range(2, int(MAX**0.5) + 1): + if is_p[p]: + is_p[p * p : MAX : p] = b"\0" * ((MAX - p * p - 1) // p + 1) + + +# ---------- 2. SCROLLING ---------- +def rotations(n: int) -> list[int]: + """返回 n 的所有旋转(数学移位版,无字符串开销)""" + if n < 10: + return [n] + s = str(n) + k = len(s) + res = [] + for i in range(k): + # 使用切片旋转,避免重复计算 + rotated = int(s[i:] + s[:i]) + res.append(rotated) + return res + + +# ---------- 3. MAIN ---------- +@timer +def main(limit: int = MAX) -> int: + # 预过滤:只要含 0,2,4,5,6,8 就可以跳过(除了2和5本身) + bad_digits = set("024568") + total = 0 + counted = set() # 用于去重,避免重复计数 + + for p in range(2, limit): + if is_p[p]: # p是素数 + # 快速数字过滤(2和5是特例) + if p != 2 and p != 5: + s = str(p) + if not bad_digits.isdisjoint(s): + continue + + # 如果已经统计过,跳过 + if p in counted: + continue + + # 检查所有旋转 + rs = rotations(p) + + # 检查所有旋转数是否都是素数 + valid = True + for r in rs: + if r >= limit or not is_p[r]: + valid = False + break + + if valid: + # 如果是循环素数,统计并标记已计数 + total += len(set(rs)) # 使用set避免重复旋转 + counted.update(rs) + + return total + + +if __name__ == "__main__": + print(f"循环素数数量: {main()}") diff --git a/solutions/0035.CircularPrimes/readme.md b/solutions/0035.CircularPrimes/readme.md new file mode 100644 index 0000000..73160f8 --- /dev/null +++ b/solutions/0035.CircularPrimes/readme.md @@ -0,0 +1,6 @@ +# 判断素数 + +这个问题也很有和判断素数,以及如何更好的记录判断结果。 +当然,如何更快计算出滚动的数字也很重要。 + +个人感觉这是个算法问题更多的问题,数学占比并不多。