注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用Ruby实现,最近在学Python,随便找点东西写写当做练习,准备改成Python3实现,顺便增加一些自己的理解。
11.斐波那契数列
1, 1, 2, 3, 5, 8…
求前13个满足x % (x各位数的和) == 0的斐波那契数
如:
2 % 2 == 0
21 % 3 == 0
144 % 9 == 0
思路1:递归
import time
def fib1(n):
if n == 0 or n == 1:
return 1
return fib1(n-1) + fib1(n-2)
def solve1(n):
ans = []
j = 0
while len(ans) < n:
while True:
f = fib1(j)
j += 1
s = sum(int(x) for x in str(f))
if f % s == 0:
ans += [f]
break
return ans
start_time = time.clock()
ans = solve1(10)
print(ans)
end_time = time.clock()
print('time cost: ', end_time - start_time)
这是斐伯那契数列最简单的想法,思路简单,但效率很低,求前13个数一时半会算不出来,前10个时间:
[1, 1, 2, 3, 5, 8, 21, 144, 2584, 14930352]
time cost: 8.75
思路2:带记忆的递归
def fib2(d, n):
if n in d:
return d[n]
if n == 0 or n == 1:
d[n] = 1
else:
d[n] = fib2(d, n-1) + fib2(d, n-2)
return d[n]
def solve2(n):
d = {}
ans = []
j = 0
while len(ans) < n:
while True:
f = fib2(d, j)
j += 1
s = sum(int(x) for x in str(f))
if f % s == 0:
ans += [f]
break
return ans
start_time = time.clock()
ans = solve2(13)
print(ans)
end_time = time.clock()
print('time cost: ', end_time - start_time)
[1, 1, 2, 3, 5, 8, 21, 144, 2584, 14930352, 86267571272, 498454011879264, 160500643816367088]
time cost: 0.0
效率快了无数倍
思路3:递推
def solve3(n):
ans = [1, 1]
a, b = 1, 1
while len(ans) < n:
f = a + b
s = sum(int(x) for x in str(f))
if f % s == 0:
ans += [f]
a, b = b, f
return ans
start_time = time.clock()
ans = solve3(13)
print(ans)
end_time = time.clock()
print('time cost: ', end_time - start_time)
[1, 1, 2, 3, 5, 8, 21, 144, 2584, 14930352, 86267571272, 498454011879264, 160500643816367088]
time cost: 0.0
12.平方根数字
2的正平方根1.414213562373095048
0~9数字都出现需要19位,求
1)包含整数部分
2)不包含整数部分
两种情况下,最早出现0~9 10个数字的数
1)包含整数部分
思路:暴力搜索
n = 1
while True:
s = format(n ** 0.5, '10.10f').split('.')
ss = (s[0] + s[1])[: 10]
if len(set(ss)) == 10:
break
n += 1
print(n, str(n ** 0.5)[:11])
1362 36.90528417
用split把数字分成整数部分和小数部分,去掉小数点,看是否出现10个数字
n = 1
while True:
s = format(n ** 0.5, '10.10f')
i = s.index('.')
s = s[:i] + s[i+1:11]
if len(set(s)) == 10:
break
n += 1
print(n, str(n ** 0.5)[:11])
1362 36.90528417
找到小数点位置,去掉
n = 1
while True:
s = format(n ** 0.5, '10.10f')
if len(set(s[:11])) == 11:
break
n += 1
print(n, str(n ** 0.5)[:11])
1362 36.90528417
不用去掉小数点
2)不包含整数部分
n = 1
while True:
s = format(n ** 0.5, '10.10f').split('.')
if len(set(s[1])) == 10:
break
n += 1
s = str(n ** 0.5)
i = s.index('.')
print(n, s[: i+11])
143 11.9582607431
n = 1
while True:
s = format(n ** 0.5, '10.10f')
i = s.index('.')
s = s[i+1 : i+11]
if len(set(s)) == 10:
break
n += 1
s = str(n ** 0.5)
i = s.index('.')
print(n, s[: i+11])
143 11.9582607431
13.表达式求值
每个字母代表一个不同的数字,求满足:
READ + WRITE + TALK = SKILL的表达式
思路1:暴力搜索
from itertools import permutations
import time
def solve1():
ans = []
for r, e, a, d, w, i, t, l, k, s in permutations([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]):
if r == 0 or w == 0 or t == 0 or s == 0:
continue
read = r*1000 + e*100 + a*10 + d
write = w*10000 + r*1000 + i*100 + t*10 + e
talk = t*1000 + a*100 + l*10 + k
skill = s*10000 + k*1000 + i*100 + l*10 + l
if read + write + talk == skill:
ans += [(read, write, talk, skill)]
return ans
start = time.perf_counter()
ans = solve1()
end = time.perf_counter()
for a in ans:
print(a[0], "+", a[1], "+", a[2], "=", a[3])
print("cost time: ", end-start)
1632 + 41976 + 7380 = 50988
2543 + 72065 + 6491 = 81099
4905 + 24689 + 8017 = 37611
5094 + 75310 + 1962 = 82366
5096 + 35710 + 1982 = 42788
5180 + 65921 + 2843 = 73944
5270 + 85132 + 3764 = 94166
7092 + 37510 + 1986 = 46588
7092 + 47310 + 1986 = 56388
9728 + 19467 + 6205 = 35400
cost time: 1.6280497459229082
思路2:使用eval
def solve2():
ans = []
for r, e, a, d, w, i, t, l, k, s in permutations(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']):
if r == '0' or w == '0' or t == '0' or s == '0':
continue
read = r + e + a + d
write = w + r + i + t + e
talk = t + a + l + k
skill = s + k + i + l + l
exp = read + "+" + write + "+" + talk + "==" + skill
if eval(exp):
ans += exp,
return ans
start = time.perf_counter()
ans = solve2()
end = time.perf_counter()
for a in ans:
print(a)
print("cost time: ", end-start)
1632+41976+7380==50988
2543+72065+6491==81099
4905+24689+8017==37611
5094+75310+1962==82366
5096+35710+1982==42788
5180+65921+2843==73944
5270+85132+3764==94166
7092+37510+1986==46588
7092+47310+1986==56388
9728+19467+6205==35400
cost time: 14.172635051072575
可以发现这个办法慢了很多
上面两个方法都不够灵活,换个表达式,代码就得变
思路:灵活的方法
import re
def solve3(expression):
nums = [ n for n in re.split(r'\W', expression) if len(n) > 0 ]
print(nums)
chars = ''.join(list(set(''.join(nums))))
print(chars)
heads = set([ w[0] for w in nums ])
print(heads)
ans = []
all_num = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
for perm in permutations(all_num, len(chars)):
seq = ''.join(perm)
index = seq.find('0')
if index >= 0 and chars[index] in heads:
continue
trans_table = str.maketrans(chars, seq)
e = expression.translate(trans_table)
if eval(e):
ans.append(e)
return ans
start = time.perf_counter()
expression = "READ+WRITE+TALK==SKILL"
ans = solve3(expression)
end = time.perf_counter()
for a in ans:
print(a)
print("cost time: ", end-start)
[‘READ’, ‘WRITE’, ‘TALK’, ‘SKILL’]
[‘D’, ‘T’, ‘S’, ‘E’, ‘K’, ‘L’, ‘W’, ‘A’, ‘R’, ‘I’]
{‘W’, ‘T’, ‘R’, ‘S’}
5180+65921+2843==73944
5270+85132+3764==94166
7092+37510+1986==46588
7092+47310+1986==56388
1632+41976+7380==50988
2543+72065+6491==81099
5094+75310+1962==82366
4905+24689+8017==37611
5096+35710+1982==42788
9728+19467+6205==35400
cost time: 17.471927992999554
思路:快速的方法,根据数字间的关系
def solve4():
ans = []
all_nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
num_set = set(all_nums)
for e, a, d, t, l, k in permutations(all_nums, 6):
if (a+t == 8 or a+t == 9 or a+t == 10) and \
(a+e == 8 or a+e == 9 or a+e == 10) and \
((d+e+k) % 10 == l) and \
(((a+t+l) * 10 + d + e + k) % 100 == l*11):
for i, r, s, w in permutations(list(num_set - {k, e, d, l, t, a}), 4):
if r == 0 or w == 0 or t == 0 or s == 0:
continue
if r != 0 and w != 0 and t != 0 and s != 0 and \
(s == w+1 or s == w+2):
read = r*1000 + e*100 + a*10 + d
write = w*10000 + r*1000 + i*100 + t*10 + e
talk = t*1000 + a*100 + l*10 + k
skill = s*10000 + k*1000 + i*100 + l*10 + l
if read + write + talk == skill:
ans += [(read, write, talk, skill)]
return ans
start = time.perf_counter()
ans = solve4()
end = time.perf_counter()
for a in ans:
print(a[0], "+", a[1], "+", a[2], "=", a[3])
print("cost time: ", end-start)
7092 + 47310 + 1986 = 56388
7092 + 37510 + 1986 = 46588
5094 + 75310 + 1962 = 82366
5096 + 35710 + 1982 = 42788
5180 + 65921 + 2843 = 73944
5270 + 85132 + 3764 = 94166
2543 + 72065 + 6491 = 81099
1632 + 41976 + 7380 = 50988
9728 + 19467 + 6205 = 35400
4905 + 24689 + 8017 = 37611
cost time: 0.025314760045148432
可以发现这是最快速的方法
14.国名接龙
给定一系列国家的名字,求不重复的最长的接龙序列。
如:Cameroon -> Netherlands -> Spain
思路:DFS,使用一个列表记录已经使用过的名字,防止重复
countries = ["Algeria", "Argentina", "Australia", "Belgium", "Bosnia and Herzegovina",
"Brazil", "Cameroon", "Chile", "Colombia", "Costa Rica", "Croatia", "Ecuador",
"England", "France", "Germany", "Ghana", "Greece", "Honduras", "Iran", "Italy",
"Cote d'Ivoire", "Japan", "Mexico", "Netherlands", "Nigeria", "Portugal", "Russia",
"Korea Republic", "Spain", "Switzerland", "USA", "Uruguay"]
def solve(countries):
def dfs(prev_country, depth):
if ans[0] < depth:
ans[0] = depth
for i, c in enumerate(countries):
if not used[i] and c[0].lower() == prev_country[-1].lower():
used[i] = True
dfs(c, depth+1)
used[i] = False
ans = [0]
used = [False] * len(countries)
for i, c in enumerate(countries):
used[i] = True
dfs(c, 1)
used[i] = False
return ans[0]
print(solve(countries))
8
如果要求找出最长的序列呢?
思路:用一个列表记录当前序列,跟找到的最长序列比较,若更长,则更新答案
import copy
def solve1(countries):
def dfs(current):
global ans
if len(ans) < len(current):
ans = copy.deepcopy(current)
for i, c in enumerate(countries):
if not used[i] and c[0].lower() == current[-1][-1].lower():
used[i] = True
dfs(current+[c])
used[i] = False
used = [False] * len(countries)
for i, c in enumerate(countries):
used[i] = True
dfs([c])
used[i] = False
ans = []
solve1(countries)
print(ans)
[‘Korea Republic’, ‘Cameroon’, ‘Netherlands’, ‘Spain’, ‘Nigeria’, ‘Algeria’, ‘Argentina’, ‘Australia’]
不使用全局变量呢?
思路:改用类的成员…
import copy
class Solution():
def solve(self, countries):
def dfs(current):
if len(self.ans) < len(current):
self.ans = copy.deepcopy(current)
for i, c in enumerate(countries):
if not used[i] and c[0].lower() == current[-1][-1].lower():
used[i] = True
dfs(current+[c])
used[i] = False
self.ans = []
used = [False] * len(countries)
for i, c in enumerate(countries):
used[i] = True
dfs([c])
used[i] = False
return self.ans
sol = Solution()
print(sol.solve(countries))
[‘Korea Republic’, ‘Cameroon’, ‘Netherlands’, ‘Spain’, ‘Nigeria’, ‘Algeria’, ‘Argentina’, ‘Australia’]
15.走楼梯
A从0层往上走,B从N曾往下走,两人都是每次可以走1,2,.. step级楼梯,问两人相遇的情况。
如N=4, step = 3,相遇的情况有:
1) 0->1->2 4->3->2
2) 0->1, 4->1
3) 0->2, 4->2
4) 0->3, 4->3
思路1:递归
def solve1(step, a, b):
if a > b:
return 0
if a == b:
return 1
cnt = 0
for i in range(1, step+1):
for j in range(1, step+1):
cnt += solve1(step, a+i, b-j)
return cnt
s = time.perf_counter()
ans = solve1(4, 0, 20)
e = time.perf_counter()
print(ans)
print("time: ", e-s)
141977
time: 0.51831706892699
思路2:带记忆的递归
def solve2(d, step, a, b):
if (a, b) in d:
return d[(a, b)]
if a > b:
d[(a, b)] = 0
elif a == b:
d[(a, b)] = 1
else:
cnt = 0
for i in range(1, step+1):
for j in range(1, step+1):
cnt += solve2(d, step, a+i, b-j)
d[(a, b)] = cnt
return d[(a, b)]
s = time.perf_counter()
d = {}
ans = solve2(d, 4, 0, 200)
e = time.perf_counter()
print(ans)
print("time: ", e-s)
284262489361244884075571372840366357335971123462886073361
time: 0.0744029360357672
发现带记忆的递归求N是普通递归10倍时,花的时间还少
思路3:DP
问题等价于一个人从0,走到N层,且走的步数为偶数的情况数
dp[i][j] 表示移动i次,到达j层的情况数,则
dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + … + dp[i-1][j-step]
def solve3(step, a, b):
dp = [[0] * (b+1) for i in range(b+1)]
dp[0][0] = 1
for i in range(b):
for j in range(b+1):
for k in range(1, step+1):
if j + k <= b:
dp[i+1][j+k] += dp[i][j]
ans = 0
for i in range(0, b+1, 2):
ans += dp[i][b]
return ans
s = time.perf_counter()
ans = solve3(4, 0, 200)
e = time.perf_counter()
print(ans)
print("time: ", e-s)
284262489361244884075571372840366357335971123462886073361
time: 0.033562466967850924
思路4:DP,一维DP
def solve4(step, a, b):
dp = [0] * (b+1)
dp[0] = 1
ans = 0
for i in range(b):
for j in range(b, -1, -1):
for k in range(1, step+1):
if k + j <= b:
dp[j+k] += dp[j]
dp[j] = 0
if i % 2 == 1:
ans += dp[b]
return ans
s = time.perf_counter()
ans = solve4(4, 0, 200)
e = time.perf_counter()
print(ans)
print("time: ", e-s)
284262489361244884075571372840366357335971123462886073361
time: 0.026876457035541534
思路5:一维DP,从上往下走
284262489361244884075571372840366357335971123462886073361
time: 0.027695992961525917
def solve5(step, a, b):
dp = [0] * (b+1)
dp[b] = 1
ans = 0
for i in range(b):
for j in range(b+1):
for k in range(1, step+1):
if k <= j:
dp[j-k] += dp[j]
dp[j] = 0
if i % 2 == 1:
ans += dp[0]
return ans
s = time.perf_counter()
ans = solve5(4, 0, 200)
e = time.perf_counter()
print(ans)
print("time: ", e-s)