diff --git a/README.md b/README.md index d036a16..6cf6ec7 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,11 @@ 也会有一些新奇的想法。主要使用 Python 解决,也会有 Haskell 、 Rust 或者 Lean4 的解决方法。 项目使用 `uv` 进行管理,毕竟主要还是使用 Python来写的。 + + +----- + +**KEY POINT :** + +要把数学回归数学,而不是回归程序。 +从数学角度去考虑问题,用数学化简计算逻辑。 diff --git a/pyproject.toml b/pyproject.toml index 085c5c4..3497034 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "projecteuler" version = "0.1.0" -description = "Add your description here" +description = "euler 项目的解题。主要为python。" readme = "README.md" requires-python = ">=3.12" dependencies = [] diff --git a/solutions/0003.largestprime/euler_3.py b/solutions/0003.largestprime/euler_3.py index 7901d8c..d787a83 100644 --- a/solutions/0003.largestprime/euler_3.py +++ b/solutions/0003.largestprime/euler_3.py @@ -1,3 +1,8 @@ +""" +The prime factors of 13195 are 5, 7, 13and 29. +What is the largest prime factor of the number 600851475143 ? +""" + import random from math import gcd from typing import List, Set @@ -34,7 +39,7 @@ def is_probable_prime(n: int, trials: int = 10) -> bool: return True -def pollards_rho(n: int, max_iter: int = 100000) -> int: +def pollards_rho(n: int, max_iter: int = 100000) -> int | None: """ Pollard's Rho 算法:返回n的一个非平凡因子 @@ -46,6 +51,7 @@ def pollards_rho(n: int, max_iter: int = 100000) -> int: n的一个因子(可能是质数也可能是合数) 若失败返回None """ + if n % 2 == 0: return 2 @@ -86,6 +92,8 @@ def factorize(n: int | None) -> List[int | None]: """ if n == 1: return [] + if n is None: + return [None] # 如果是质数,直接返回 if is_probable_prime(n): @@ -94,6 +102,9 @@ def factorize(n: int | None) -> List[int | None]: # 获取一个因子 factor = pollards_rho(n) + if factor is None: + return [None] + # 递归分解 return factorize(factor) + factorize(n // factor) @@ -101,3 +112,7 @@ def factorize(n: int | None) -> List[int | None]: def get_prime_factors(n: int) -> Set[int | None]: """获取所有不重复的质因数""" return set(factorize(n)) + + +if __name__ == "__main__": + print(get_prime_factors(60)) # {2, 3, 5} diff --git a/solutions/0004.palindrome/eular_4.py b/solutions/0004.palindrome/eular_4.py new file mode 100644 index 0000000..3ecbce9 --- /dev/null +++ b/solutions/0004.palindrome/eular_4.py @@ -0,0 +1,52 @@ +""" +A palindromic number reads the same both ways. +The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99. +Find the largest palindrome made from the product of two 3-digit numbers. +""" + + +def is_palindrome(n: int) -> bool: + return str(n) == str(n)[::-1] + + +def initial_idea() -> None: + a = 999 + b = 999 + y = (0, 0) + max_palindrome = 0 + for i in range(a, 99, -1): + for j in range(b, 99, -1): + product = i * j + if is_palindrome(product) and product > max_palindrome: + max_palindrome = product + y = (i, j) + print(f"{max_palindrome} = {y[0]} × {y[1]}") + + +def largest_palindrome_product(): + max_palindrome = 0 + max_factors = (0, 0) + + # 外层循环从大到小,且只遍历11的倍数 + for i in range(990, 100, -11): # 从990开始(最大的11的倍数) + # 内层循环从i开始(避免重复,利用乘法交换律) + for j in range(999, i - 1, -1): + product = i * j + + # 提前终止:如果乘积已小于当前最大值 + if product <= max_palindrome: + break + + # 检查是否为回文数 + if str(product) == str(product)[::-1]: + max_palindrome = product + max_factors = (i, j) + break # 找到即可跳出内层循环 + + return max_palindrome, max_factors + + +if __name__ == "__main__": + initial_idea() + x, y = largest_palindrome_product() + print(f"{x} = {y[0]} × {y[1]}") diff --git a/solutions/0004.palindrome/readme.md b/solutions/0004.palindrome/readme.md new file mode 100644 index 0000000..3583ac7 --- /dev/null +++ b/solutions/0004.palindrome/readme.md @@ -0,0 +1,48 @@ +从数学角度,**快速**找到两个三位数相乘得到的最大回文数。 + +## 核心数学洞察 + +首先,两个三位数最大的乘积是: 999 × 999 = 998001 。所以最大的回文数一定是6位的。 + +**1. 回文数的结构性质** + +一个6位回文数可以表示为: +$$ +\overline{abccba} = 100000a + 10000b + 1000c + 100c + 10b + a = 100001a + 10010b + 1100c = 11 \times (9091a + 910b + 100c) +$$ + +**关键结论**:所有6位回文数都是**11的倍数**。 + +**2. 质因数推论** + +如果乘积 $p \times q$ 是回文数,且这个回文数是11的倍数,那么: +- 由于11是质数,**p和q中至少有一个是11的倍数** +- 这样搜索空间直接缩小为原来的1/11 + +## 最优算法策略 + +```python +def largest_palindrome_product(): + max_palindrome = 0 + max_factors = (0, 0) + + # 外层循环从大到小,且只遍历11的倍数 + for i in range(990, 100, -11): # 从990开始(最大的11的倍数) + # 内层循环从i开始(避免重复,利用乘法交换律) + for j in range(999, i-1, -1): + product = i * j + + # 提前终止:如果乘积已小于当前最大值 + if product <= max_palindrome: + break + + # 检查是否为回文数 + if str(product) == str(product)[::-1]: + max_palindrome = product + max_factors = (i, j) + break # 找到即可跳出内层循环 + + return max_palindrome, max_factors + +# 结果:906609 = 913 × 993 +``` diff --git a/solutions/0005.smallestMultiple/eular_5.py b/solutions/0005.smallestMultiple/eular_5.py new file mode 100644 index 0000000..a72f9f6 --- /dev/null +++ b/solutions/0005.smallestMultiple/eular_5.py @@ -0,0 +1,67 @@ +""" +2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder. +What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20? +""" + +import math +import random + + +def is_probable_prime(n: int, trials: int = 10) -> bool: + """Miller-Rabin素性测试(快速判断是否为质数)""" + if n < 2: + return False + if n in (2, 3): + return True + if n % 2 == 0: + return False + + # 将 n-1 写成 d * 2^s 的形式 + d = n - 1 + s = 0 + while d % 2 == 0: + d //= 2 + s += 1 + + # 测试 + for _ in range(trials): + a = random.randrange(2, n - 1) + x = pow(a, d, n) + if x == 1 or x == n - 1: + continue + for _ in range(s - 1): + x = pow(x, 2, n) + if x == n - 1: + break + else: + return False + return True + + +def primes_up_to(n: int) -> list[int]: + if n < 2: + return [] + if n == 2: + return [2] + if n == 3: + return [2, 3] + + primes = [2, 3] + for i in range(5, n + 1, 2): + if is_probable_prime(i): + primes.append(i) + + return primes + + +def smallest_multiple(n: int) -> int: + result = 1 + for p in primes_up_to(n): + # 找出最大幂次 + exp = int(math.log(n, p)) + result *= p**exp + return result + + +if __name__ == "__main__": + print(smallest_multiple(20)) diff --git a/solutions/0005.smallestMultiple/readme.md b/solutions/0005.smallestMultiple/readme.md new file mode 100644 index 0000000..58a4a20 --- /dev/null +++ b/solutions/0005.smallestMultiple/readme.md @@ -0,0 +1,11 @@ +想法来自: [A003418](https://oeis.org/A003418) 。 + +对于 [a .. b] 这个连续整数区间,最小的可整除的整数,就是: + +**小于b的所有质数分别小于b的最大幂值的乘积** 。 + +计算步骤就是: + +1. 找出不超过 $b$ 的所有质数 ${p}$ +2. 对每个质数 $p$ ,找出最大指数 $e$ 使得 $p^e \leq b$ +3. 将所有 $p^e$ 相乘