feat(0052): 添加欧拉52题“排列倍数”的最优算法实现
This commit is contained in:
67
solutions/0052.PermutedMultiples/README.md
Normal file
67
solutions/0052.PermutedMultiples/README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
欧拉计划第52题(**排列倍数**)的答案是 **142857**。
|
||||||
|
|
||||||
|
最快的数学计算方式基于以下**三大核心数学原理**的级联优化:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. 位数约束原理(范围剪枝)
|
||||||
|
**数学基础**:若 $x$ 与 $6x$ 位数相同,则必须满足:
|
||||||
|
$$10^{n-1} \leq x < \frac{10^n}{6} \approx 1.666... \times 10^{n-1}$$
|
||||||
|
|
||||||
|
这直接排除了 $60\%$ 以上的搜索空间。例如6位数时,只需检查 $[100000, 166666)$ 区间,而非 $[100000, 1000000)$。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. 模9同余原理(整除筛选)
|
||||||
|
**数学基础**:数字和与模9同余(数根理论)。若 $x$ 与 $kx$ 是数字排列,则:
|
||||||
|
$$\sum \text{digits}(x) \equiv x \pmod{9} \equiv kx \equiv \sum \text{digits}(kx) \pmod{9}$$
|
||||||
|
|
||||||
|
由此推出 $(k-1)x \equiv 0 \pmod{9}$。取 $k=2$,得:
|
||||||
|
$$x \equiv 0 \pmod{9}$$
|
||||||
|
|
||||||
|
**关键优化**:只需检查9的倍数,搜索空间再缩减 $89\%$。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. 规范形签名原理(排列判定)
|
||||||
|
**数学基础**:两个数是排列 ⟺ 它们的**数字频数向量**(Digit Histogram)相同。
|
||||||
|
|
||||||
|
不采用字符串排序($O(d \log d)$),而是用**计数数组**作为规范签名($O(d)$):
|
||||||
|
```python
|
||||||
|
def signature(n):
|
||||||
|
count = [0] * 10
|
||||||
|
while n:
|
||||||
|
count[n % 10] += 1
|
||||||
|
n //= 10
|
||||||
|
return tuple(count) # 如142857 → (1,1,1,1,1,1,0,1,0,0)的某种编码
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 最优算法实现(Python)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def euler52():
|
||||||
|
n = 1
|
||||||
|
while True:
|
||||||
|
start = 10**(n-1)
|
||||||
|
# 核心约束:x * 6 必须仍是n位数
|
||||||
|
end = int(10**n / 6) + 1
|
||||||
|
|
||||||
|
# 步长为9:利用模9同余原理
|
||||||
|
for x in range((start + 8) // 9 * 9, end, 9):
|
||||||
|
sig = signature(x)
|
||||||
|
# 检查2x到6x是否共享相同签名
|
||||||
|
if all(signature(k * x) == sig for k in range(2, 7)):
|
||||||
|
return x
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
# 结果:142857(即1/7的小数循环节)
|
||||||
|
```
|
||||||
|
|
||||||
|
**复杂度**:对于 $n$ 位数,候选量从 $9 \times 10^{n-1}$ 降至约 $0.074 \times 10^{n-1}$,配合 $O(n)$ 的签名比较,可在**微秒级**求解。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 深层数学结构
|
||||||
|
解 **142857** 并非偶然,它是**循环数**(Cyclic Number),与 $1/7 = 0.\overline{142857}$ 的小数展开密切相关。其性质源于 $10$ 是模 $7$ 的原根,导致 $2/7, 3/7...6/7$ 的循环节恰好是 $142857$ 的轮换排列。
|
||||||
80
solutions/0052.PermutedMultiples/euler_52.py
Normal file
80
solutions/0052.PermutedMultiples/euler_52.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
"""
|
||||||
|
It can be seen that the number, 125874, and its double, 251748,
|
||||||
|
contain exactly the same digits, but in a different order.
|
||||||
|
|
||||||
|
Find the smallest positive integer, x, such that
|
||||||
|
2x, 3x, 4x, 5x, and 6x contain the same digits.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def timer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start_time = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end_time = time.time()
|
||||||
|
elapsed_time = end_time - start_time
|
||||||
|
print(f"{func.__name__} time: {elapsed_time:.6f} seconds")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def has_same_digits(x, y):
|
||||||
|
return sorted(str(x)) == sorted(str(y))
|
||||||
|
|
||||||
|
|
||||||
|
def signature(n):
|
||||||
|
count = [0] * 10
|
||||||
|
while n:
|
||||||
|
count[n % 10] += 1
|
||||||
|
n //= 10
|
||||||
|
return tuple(count)
|
||||||
|
|
||||||
|
|
||||||
|
def find_smallest_permuted_multiple():
|
||||||
|
x = 1
|
||||||
|
while True:
|
||||||
|
if not x % 9 == 0:
|
||||||
|
x += 1
|
||||||
|
continue
|
||||||
|
for i in range(2, 7):
|
||||||
|
if not has_same_digits(x, x * i):
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return x
|
||||||
|
x += 1
|
||||||
|
|
||||||
|
|
||||||
|
def better_permuted_multiple():
|
||||||
|
n = 1
|
||||||
|
while True:
|
||||||
|
start = 10 ** (n - 1)
|
||||||
|
# 核心约束:x * 6 必须仍是n位数
|
||||||
|
end = int(10**n / 6) + 1
|
||||||
|
|
||||||
|
# 步长为9:利用模9同余原理
|
||||||
|
for x in range((start + 8) // 9 * 9, end, 9):
|
||||||
|
sig = signature(x)
|
||||||
|
# 检查2x到6x是否共享相同签名
|
||||||
|
if all(signature(k * x) == sig for k in range(2, 7)):
|
||||||
|
return x
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main():
|
||||||
|
result = find_smallest_permuted_multiple()
|
||||||
|
print(f"{result}")
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main_better():
|
||||||
|
result = better_permuted_multiple()
|
||||||
|
print(f"{result}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
main_better()
|
||||||
Reference in New Issue
Block a user