✨ feat:添加欧拉项目第30题和第31题的解决方案
📝 docs:为第31题添加详细的动态规划算法说明文档
This commit is contained in:
91
solutions/0030.FifthPowers/euler_30.py
Normal file
91
solutions/0030.FifthPowers/euler_30.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
"""
|
||||||
|
Surprisingly there are only three numbers that can be written as the sum of fourth powers of their digits:
|
||||||
|
|
||||||
|
1634 = 1^4 + 6^4 + 3^4 + 4^4
|
||||||
|
8208 = 8^4 + 2^4 + 0^4 + 8^4
|
||||||
|
9474 = 9^4 + 4^4 + 7^4 + 4^4
|
||||||
|
|
||||||
|
As 1 = 1^4 is not a sum it is not included.
|
||||||
|
|
||||||
|
The sum of these numbers is 19316.
|
||||||
|
|
||||||
|
Find the sum of all the numbers that can be written as the sum of fifth powers of their digits.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
from itertools import combinations_with_replacement
|
||||||
|
|
||||||
|
|
||||||
|
def timer(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
start = time.time()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
end = time.time()
|
||||||
|
print(f"{func.__name__} on ({kwargs}) taken: {end - start: .6f} seconds")
|
||||||
|
return result
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def fifth_power(n: int, power: int = 5, strict: bool = False) -> tuple[bool, int]:
|
||||||
|
sumx = sum(int(digit) ** power for digit in str(n))
|
||||||
|
if strict:
|
||||||
|
isok = (sumx == n) and (power == len(str(sumx)))
|
||||||
|
return isok, sumx
|
||||||
|
return sumx == n, sumx
|
||||||
|
|
||||||
|
|
||||||
|
def top_number(n: int) -> int:
|
||||||
|
beg = (9**n) * n
|
||||||
|
beg_len = len(str(beg))
|
||||||
|
last = n
|
||||||
|
while True:
|
||||||
|
if beg_len >= n:
|
||||||
|
beg = (9**n) * beg_len
|
||||||
|
last = beg_len
|
||||||
|
beg_len = len(str(beg))
|
||||||
|
if beg_len == last:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return int("9" * beg_len)
|
||||||
|
|
||||||
|
|
||||||
|
def is_match(num: int, comb: list[str]) -> bool:
|
||||||
|
if num in [1, 0]:
|
||||||
|
return False
|
||||||
|
numstr = str(num).zfill(len(comb))
|
||||||
|
numstr = sorted(numstr)
|
||||||
|
return numstr == sorted(comb)
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main_just_compute(power: int, strict: bool = False) -> None:
|
||||||
|
res = []
|
||||||
|
top = top_number(power)
|
||||||
|
for i in range(2, top + 1):
|
||||||
|
is_sum, sumx = fifth_power(i, power, strict)
|
||||||
|
if is_sum:
|
||||||
|
print(sumx, end="\t")
|
||||||
|
res.append(sumx)
|
||||||
|
print(f"\nThe sum is {sum(res):,d}")
|
||||||
|
|
||||||
|
|
||||||
|
@timer
|
||||||
|
def main_combinations(power: int) -> None:
|
||||||
|
top_len = len(str(top_number(power)))
|
||||||
|
res = set()
|
||||||
|
combs = combinations_with_replacement("0123456789", top_len)
|
||||||
|
for comb in combs:
|
||||||
|
tmp = sum(map(lambda x: int(x) ** power, comb))
|
||||||
|
if is_match(tmp, list(comb)):
|
||||||
|
print(tmp, end="\t")
|
||||||
|
res.add(tmp)
|
||||||
|
print(f"\nThe sum is {sum(res):,d}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
the_power = 5
|
||||||
|
main_just_compute(power=the_power, strict=False)
|
||||||
|
print("=" * 50)
|
||||||
|
main_combinations(power=the_power)
|
||||||
27
solutions/0031/euler_31.py
Normal file
27
solutions/0031/euler_31.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"""
|
||||||
|
In the United Kingdom the currency is made up of pound (£) and pence (p).
|
||||||
|
There are eight coins in general circulation:
|
||||||
|
|
||||||
|
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p), and £2 (200p).
|
||||||
|
|
||||||
|
It is possible to make £2 in the following way:
|
||||||
|
|
||||||
|
1×£1 + 1×50p + 2×20p + 1×5p + 1×2p + 3×1p
|
||||||
|
|
||||||
|
How many different ways can £2 be made using any number of coins?
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
coins = [1, 2, 5, 10, 20, 50, 100, 200]
|
||||||
|
ways = [1] + [0] * 200
|
||||||
|
|
||||||
|
for coin in coins:
|
||||||
|
for i in range(coin, 201):
|
||||||
|
ways[i] += ways[i - coin]
|
||||||
|
|
||||||
|
print(f"\nThe number of ways to make £2 is {ways[200]:,d}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
114
solutions/0031/readme.md
Normal file
114
solutions/0031/readme.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# 动态规划法
|
||||||
|
|
||||||
|
## 核心思路
|
||||||
|
|
||||||
|
这个函数采用**动态规划**的思想,通过构建一个 `ways` 数组来累积计算每个金额对应的方法数。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 逐行解析
|
||||||
|
|
||||||
|
### 1. 初始化
|
||||||
|
```python
|
||||||
|
coins = [1, 2, 5, 10, 20, 50, 100, 200]
|
||||||
|
ways = [1] + [0] * 200 # 结果是 [1, 0, 0, 0, ..., 0] (共201个元素)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `coins`: 所有可用的硬币面值(单位:便士)
|
||||||
|
- `ways[i]`: 表示凑成金额 `i` 的方法数
|
||||||
|
- **关键点**:`ways[0] = 1` 表示凑成0元有1种方法(什么都不用),这是动态规划的**基准条件**
|
||||||
|
|
||||||
|
### 2. 双重循环的核心逻辑
|
||||||
|
```python
|
||||||
|
for coin in coins: # 按顺序遍历每种硬币
|
||||||
|
for i in range(coin, 201): # 从当前硬币面值遍历到200
|
||||||
|
ways[i] += ways[i - coin]
|
||||||
|
```
|
||||||
|
|
||||||
|
这就是**状态转移方程**,其含义是:
|
||||||
|
|
||||||
|
> **凑成金额 `i` 的方法数 = 原来方法数 + 使用当前硬币的方法数**
|
||||||
|
|
||||||
|
其中 `ways[i - coin]` 表示:在使用了1枚当前硬币后,凑齐剩余金额的方法数。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 具体执行过程演示
|
||||||
|
|
||||||
|
让我们跟踪计算前几个值的变化,以理解算法如何工作:
|
||||||
|
|
||||||
|
### 初始状态
|
||||||
|
```
|
||||||
|
ways = [1, 0, 0, 0, 0, 0, ...] # ways[0]=1
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第1轮:使用硬币 1p
|
||||||
|
```python
|
||||||
|
for i in range(1, 201):
|
||||||
|
ways[i] += ways[i-1]
|
||||||
|
```
|
||||||
|
|
||||||
|
- `i=1`: `ways[1] += ways[0]` → `0 + 1 = 1` ✓ 凑1p: {1}
|
||||||
|
- `i=2`: `ways[2] += ways[1]` → `0 + 1 = 1` ✓ 凑2p: {1,1}
|
||||||
|
- `i=3`: `ways[3] += ways[2]` → `0 + 1 = 1` ✓ 凑3p: {1,1,1}
|
||||||
|
- ...
|
||||||
|
- **结果**:只用1p硬币,每个金额都有且仅有1种方法
|
||||||
|
|
||||||
|
### 第2轮:加入硬币 2p
|
||||||
|
```python
|
||||||
|
for i in range(2, 201):
|
||||||
|
ways[i] += ways[i-2]
|
||||||
|
```
|
||||||
|
|
||||||
|
关键更新点:
|
||||||
|
- `i=2`: `ways[2] += ways[0]` → `1 + 1 = 2`
|
||||||
|
- 原来:{1,1}
|
||||||
|
- 新增:{2}
|
||||||
|
- `i=3`: `ways[3] += ways[1]` → `1 + 1 = 2`
|
||||||
|
- 原来:{1,1,1}
|
||||||
|
- 新增:{1,2}
|
||||||
|
- `i=4`: `ways[4] += ways[2]` → `1 + 2 = 3`
|
||||||
|
- 原来:{1,1,1,1}
|
||||||
|
- 新增:{1,1,2}, {2,2}
|
||||||
|
|
||||||
|
### 第3轮:加入硬币 5p
|
||||||
|
当加入5p硬币后,凑5p的方法从1种 {1,1,1,1,1} 变成2种,新增 {5}。
|
||||||
|
|
||||||
|
**以此类推**,直到处理完所有硬币类型。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 为什么这样计算?
|
||||||
|
|
||||||
|
这个算法的精妙之处在于:
|
||||||
|
|
||||||
|
1. **按硬币顺序处理**:确保计算的是**组合数**(不考虑顺序)
|
||||||
|
- {1,2,2} 和 {2,1,2} 视为同一种方法
|
||||||
|
- 如果调换循环顺序(先金额后硬币),会计算出排列数
|
||||||
|
|
||||||
|
2. **累积效应**:`ways` 数组保存的是**所有已处理硬币**能凑成的方法总数
|
||||||
|
|
||||||
|
3. **避免重复**:每种硬币只被考虑一次在其对应的外层循环中,确保不重复计算
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最终结果
|
||||||
|
|
||||||
|
当循环结束后,`ways[200]` 的值就是 200(便士)能被凑成的所有方法数。
|
||||||
|
|
||||||
|
```python
|
||||||
|
print(f"\nThe number of ways to make £2 is {ways[200]:,d}")
|
||||||
|
```
|
||||||
|
|
||||||
|
输出结果是:**73682**
|
||||||
|
|
||||||
|
这意味着用这8种英国硬币凑成2英镑共有**73,682**种不同的方式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 时间复杂度
|
||||||
|
|
||||||
|
- **O(N×M)**:其中 N = 硬币种类数(8),M = 目标金额(200)
|
||||||
|
- 实际计算量约为 8 × 200 = 1600 次操作,效率极高
|
||||||
|
|
||||||
|
这个算法简洁优雅,展示了动态规划在组合计数问题中的强大威力。
|
||||||
Reference in New Issue
Block a user