注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用Ruby实现,最近在学Python,随便找点东西写写当做练习,准备改成Python3实现,顺便增加一些自己的理解。
三根绳子折成3个四边形,一根摆成正方形,两根摆成长方形,会出现长方形面积之和等于正方形面积的情况,如:
绳子长度25,
1*9 = 9
2*8 = 16
5*5 = 25
如果将同比整数倍的结果看成同一种解法,如
绳长40,
2*18 = 36
4*16 = 64
10*10 = 100
跟上面看成一种解法。
求绳子长度从1到500时,共有多少种组合满足条件。
思路1:暴力搜索
def solve1(n):
ans = set()
for c in range(1, n // 4 + 1):
s = c * c
for a in range(1, c):
sa = a * (2*c - a)
for b in range(1, a):
sb = b * (2*c - b)
if s == sa + sb:
ans |= {1.0 * sb / sa}
return ans
s = time.perf_counter()
ans = solve1(2000)
e = time.perf_counter()
print(ans)
print(len(ans))
print('time cost', e - s)
{0.5625, 0.1736111111111111, 0.28444444444444444, 0.9070294784580499, 0.3871604938271605, 0.34725765306122447, 0.13270408163265307, 0.6702917136896823, 0.011141975308641975, 0.3296627824454568, 0.015747789311803154, 0.4797511291017785, 0.36922501929012347, 0.19411048099138836, 0.47175207756232684, 0.02395124716553288, 0.7616528925619834, 0.040812162024283234, 0.7024036281179138, 0.6501585714508897, 0.043737287352071004, 0.126303585159554, 0.03817262198997246, 0.013937114197530864, 0.8539740114795918, 0.006420529257067719, 0.8397223140495867, 0.012422241179346106, 0.20825459813555053, 0.09932047750229568, 0.10488765495867769, 0.07370856452570586, 0.05692828562634657, 0.005502052892162782, 0.09419090665288689, 0.007590105601469238, 0.004767573696145125, 0.9544775796398892, 0.03361111111111111, 0.3624120535564479, 0.4620445127942244, 0.14142742347870552, 0.15105967971938566, 0.14708968233471073, 0.08506944444444445, 0.11755102040816326, 0.050625, 0.01793686224489796, 0.028167636559244952, 0.2185866081969978, 0.24817185256745697, 0.059295412379827966, 0.009111570247933885, 0.6842695717993079, 0.4921562130177515, 0.26278568513417, 0.1096911753963036, 0.020618014464168312, 0.4347301050597754, 0.03694840523854657, 0.15625743944636677, 0.23765625, 0.6363584711163058, 0.07722548476454294, 0.25483080621301774, 0.06449987402368355, 0.07042899408284024, 0.4500173611111111, 0.04532008881984166, 0.010050188126959002, 0.41465794306703396, 0.9834027777777777, 0.3090051903114187, 0.7851929012345679, 0.008298719613869493, 0.8150077160493827, 0.7268655489809336, 0.20177871829334554, 0.31976332199546487, 0.004171006944444444}
80
time cost 1.9866863046772778
思路2:可用面积组合
def solve2(n):
ans = set()
for c in range(1, n // 4 + 1):
s = c * c
cands = [ i * (2*c - i) for i in range(1, c) ]
for sa, sb in combinations(cands, 2):
if sa + sb == s:
ans |= {1.0 * sa / sb}
return ans
s = time.perf_counter()
ans = solve2(2000)
e = time.perf_counter()
print(ans)
print(len(ans))
print('time cost', e - s)
{0.5625, 0.1736111111111111, 0.28444444444444444, 0.9070294784580499, 0.3871604938271605, 0.34725765306122447, 0.13270408163265307, 0.6702917136896823, 0.011141975308641975, 0.3296627824454568, 0.015747789311803154, 0.4797511291017785, 0.36922501929012347, 0.19411048099138836, 0.47175207756232684, 0.02395124716553288, 0.7616528925619834, 0.040812162024283234, 0.7024036281179138, 0.6501585714508897, 0.043737287352071004, 0.126303585159554, 0.03817262198997246, 0.013937114197530864, 0.8539740114795918, 0.006420529257067719, 0.8397223140495867, 0.012422241179346106, 0.20825459813555053, 0.09932047750229568, 0.10488765495867769, 0.07370856452570586, 0.05692828562634657, 0.005502052892162782, 0.09419090665288689, 0.007590105601469238, 0.004767573696145125, 0.9544775796398892, 0.03361111111111111, 0.3624120535564479, 0.4620445127942244, 0.14142742347870552, 0.15105967971938566, 0.14708968233471073, 0.08506944444444445, 0.11755102040816326, 0.050625, 0.01793686224489796, 0.028167636559244952, 0.2185866081969978, 0.24817185256745697, 0.059295412379827966, 0.009111570247933885, 0.6842695717993079, 0.4921562130177515, 0.26278568513417, 0.1096911753963036, 0.020618014464168312, 0.4347301050597754, 0.03694840523854657, 0.15625743944636677, 0.23765625, 0.6363584711163058, 0.07722548476454294, 0.25483080621301774, 0.06449987402368355, 0.07042899408284024, 0.4500173611111111, 0.04532008881984166, 0.010050188126959002, 0.41465794306703396, 0.9834027777777777, 0.3090051903114187, 0.7851929012345679, 0.008298719613869493, 0.8150077160493827, 0.7268655489809336, 0.20177871829334554, 0.31976332199546487, 0.004171006944444444}
80
time cost 0.9592412677593529
思路3:可用边长组合
def solve3(n):
ans = set()
for c in range(1, n // 4 + 1):
s = c * c
cands = [ i for i in range(1, c) ]
for a, b in combinations(cands, 2):
sa, sb = a*a, b*b
if sa + sb == s:
ans |= {1.0 * sa / sb}
return ans
s = time.perf_counter()
ans = solve3(2000)
e = time.perf_counter()
print(ans)
print(len(ans))
print('time cost', e - s)
{0.5625, 0.1736111111111111, 0.28444444444444444, 0.9070294784580499, 0.3871604938271605, 0.34725765306122447, 0.13270408163265307, 0.6702917136896823, 0.011141975308641975, 0.3296627824454568, 0.015747789311803154, 0.4797511291017785, 0.36922501929012347, 0.19411048099138836, 0.47175207756232684, 0.02395124716553288, 0.7616528925619834, 0.040812162024283234, 0.7024036281179138, 0.6501585714508897, 0.043737287352071004, 0.126303585159554, 0.03817262198997246, 0.013937114197530864, 0.8539740114795918, 0.006420529257067719, 0.8397223140495867, 0.012422241179346106, 0.20825459813555053, 0.09932047750229568, 0.10488765495867769, 0.07370856452570586, 0.05692828562634657, 0.005502052892162782, 0.09419090665288689, 0.007590105601469238, 0.004767573696145125, 0.9544775796398892, 0.03361111111111111, 0.3624120535564479, 0.4620445127942244, 0.14142742347870552, 0.15105967971938566, 0.14708968233471073, 0.08506944444444445, 0.11755102040816326, 0.050625, 0.01793686224489796, 0.028167636559244952, 0.2185866081969978, 0.24817185256745697, 0.059295412379827966, 0.009111570247933885, 0.6842695717993079, 0.4921562130177515, 0.26278568513417, 0.1096911753963036, 0.020618014464168312, 0.4347301050597754, 0.03694840523854657, 0.15625743944636677, 0.23765625, 0.6363584711163058, 0.07722548476454294, 0.25483080621301774, 0.06449987402368355, 0.07042899408284024, 0.4500173611111111, 0.04532008881984166, 0.010050188126959002, 0.41465794306703396, 0.9834027777777777, 0.3090051903114187, 0.7851929012345679, 0.008298719613869493, 0.8150077160493827, 0.7268655489809336, 0.20177871829334554, 0.31976332199546487, 0.004171006944444444}
80
time cost 1.8917626161128283
思路4:使用GCD 判断是否是同比组合
def solve4(n):
ans = []
for c in range(1, n // 4 + 1):
s = c * c
cands = [ i for i in range(1, c) ]
for a, b in combinations(cands, 2):
if a*a + b*b == s:
if math.gcd(a, b) == 1:
ans.append((a, b, c))
return ans
s = time.perf_counter()
ans = solve4(2000)
e = time.perf_counter()
print(ans)
print(len(ans))
print('time cost', e - s)
[(3, 4, 5), (5, 12, 13), (8, 15, 17), (7, 24, 25), (20, 21, 29), (12, 35, 37), (9, 40, 41), (28, 45, 53), (11, 60, 61), (16, 63, 65), (33, 56, 65), (48, 55, 73), (13, 84, 85), (36, 77, 85), (39, 80, 89), (65, 72, 97), (20, 99, 101), (60, 91, 109), (15, 112, 113), (44, 117, 125), (88, 105, 137), (17, 144, 145), (24, 143, 145), (51, 140, 149), (85, 132, 157), (119, 120, 169), (52, 165, 173), (19, 180, 181), (57, 176, 185), (104, 153, 185), (95, 168, 193), (28, 195, 197), (84, 187, 205), (133, 156, 205), (21, 220, 221), (140, 171, 221), (60, 221, 229), (105, 208, 233), (120, 209, 241), (32, 255, 257), (23, 264, 265), (96, 247, 265), (69, 260, 269), (115, 252, 277), (160, 231, 281), (161, 240, 289), (68, 285, 293), (136, 273, 305), (207, 224, 305), (25, 312, 313), (75, 308, 317), (36, 323, 325), (204, 253, 325), (175, 288, 337), (180, 299, 349), (225, 272, 353), (27, 364, 365), (76, 357, 365), (252, 275, 373), (135, 352, 377), (152, 345, 377), (189, 340, 389), (228, 325, 397), (40, 399, 401), (120, 391, 409), (29, 420, 421), (87, 416, 425), (297, 304, 425), (145, 408, 433), (84, 437, 445), (203, 396, 445), (280, 351, 449), (168, 425, 457), (261, 380, 461), (31, 480, 481), (319, 360, 481), (44, 483, 485), (93, 476, 485), (132, 475, 493), (155, 468, 493)]
80
time cost 1.7553507732227445
思路5:设正方形边长c,则长方形长和宽和为 c*2
设分别为c+x, c-x, 则s1 = c*c - x*x,则另一个长方形面积s2 = c*c - c*c + x*x = x*x,
即s2是平方数,同理s1也是平方数。
因此可以事先算好范围内所有平方数,然后查找判断
def solve5(n):
ans = []
squars = { i*i : i for i in range(n//4 + 1) }
for c in range(1, n // 4 + 1):
s = c * c
for a in range(1, c):
sa = a*a
sb = s - sa
if sb in squars and sb < sa:
b = squars[s - a*a]
if math.gcd(a, b) == 1:
ans.append((a, b, c))
return ans
s = time.perf_counter()
ans = solve5(2000)
e = time.perf_counter()
print(ans)
print(len(ans))
print('time cost', e - s)
[(4, 3, 5), (12, 5, 13), (15, 8, 17), (24, 7, 25), (21, 20, 29), (35, 12, 37), (40, 9, 41), (45, 28, 53), (60, 11, 61), (56, 33, 65), (63, 16, 65), (55, 48, 73), (77, 36, 85), (84, 13, 85), (80, 39, 89), (72, 65, 97), (99, 20, 101), (91, 60, 109), (112, 15, 113), (117, 44, 125), (105, 88, 137), (143, 24, 145), (144, 17, 145), (140, 51, 149), (132, 85, 157), (120, 119, 169), (165, 52, 173), (180, 19, 181), (153, 104, 185), (176, 57, 185), (168, 95, 193), (195, 28, 197), (156, 133, 205), (187, 84, 205), (171, 140, 221), (220, 21, 221), (221, 60, 229), (208, 105, 233), (209, 120, 241), (255, 32, 257), (247, 96, 265), (264, 23, 265), (260, 69, 269), (252, 115, 277), (231, 160, 281), (240, 161, 289), (285, 68, 293), (224, 207, 305), (273, 136, 305), (312, 25, 313), (308, 75, 317), (253, 204, 325), (323, 36, 325), (288, 175, 337), (299, 180, 349), (272, 225, 353), (357, 76, 365), (364, 27, 365), (275, 252, 373), (345, 152, 377), (352, 135, 377), (340, 189, 389), (325, 228, 397), (399, 40, 401), (391, 120, 409), (420, 29, 421), (304, 297, 425), (416, 87, 425), (408, 145, 433), (396, 203, 445), (437, 84, 445), (351, 280, 449), (425, 168, 457), (380, 261, 461), (360, 319, 481), (480, 31, 481), (476, 93, 485), (483, 44, 485), (468, 155, 493), (475, 132, 493)]
80
time cost 0.010256188921630383
该项目中若多个女生连续排列,体力上会出于劣势。求30人排成一排,有多少有利的排列?(只考虑男女排列,不考虑具体人)。如四个人时有8种组合:
BBBB
BBBG
BBGB
BGBB
GBBB
BGBG
GBBG
GBGB
思路:如果已有n个人,如果最右边是男生,则可以加一个男生或一个女生;如果最右边是女生,则只能加一个男生。
1)用一个串表示队伍,则当队伍还没人,或者队伍最右边是男生时可以加一个女生,而无论男女都可以加男生:
def solve1(n, current):
if len(current) == n:
return 1
ans = solve1(n, current + 'B')
if len(current) == 0 or current[-1] == 'B':
ans += solve1(n, current + 'G')
return ans
s = time.perf_counter()
ans = solve1(30, '')
e = time.perf_counter()
print(ans)
print('time cost: ', e-s)
2178309
time cost: 1.422395246103406
2)类似
def solve2(n, current):
if len(current) == n:
return 1
ans = solve1(n, current + 'B')
if current[-1] == 'B':
ans += solve1(n, current + 'G')
return ans
s = time.perf_counter()
ans = solve2(30, 'B') + solve2(30, 'G')
e = time.perf_counter()
print(ans)
print('time cost: ', e-s)
2178309
time cost: 1.42152512492612
3)只记录最后一人是男还是女
def solve3(n, last):
if n == 0:
return 1
if last == 'B':
return solve3(n-1, 'B') + solve3(n-1, 'G')
else:
return solve3(n-1, 'B')
s = time.perf_counter()
ans = solve3(29, 'B') + solve3(29, 'G')
e = time.perf_counter()
print(ans)
print('time cost: ', e-s)
2178309
time cost: 0.6548308739438653
4)
def solve4(n, d):
if n == 1 or n == 2:
return n+1
if n in d:
return d[n]
d[n] = solve4(n-1, d) + solve4(n-2, d)
return d[n]
s = time.perf_counter()
d = {}
ans = solve4(30, d)
e = time.perf_counter()
print(ans)
print('time cost: ', e-s)
2178309
time cost: 1.64741650223732e-05
思路:斐伯那契数列
从4)及打印前几个数的规律,发现可以用斐伯那契数列求解
for i in range(10):
print(solve1(i, ''), end=', ')
print()
1, 2, 3, 5, 8, 13, 21, 34, 55, 89,
def solve5(n):
b, g = 1, 0
for i in range(n):
b, g = b + g, b
return b + g
s = time.perf_counter()
ans = solve5(30)
e = time.perf_counter()
print(ans)
print('time cost: ', e-s)
2178309
time cost: 3.1921081244945526e-06
一个圆形蛋糕边缘均匀点缀着草莓,要求切成N块,N块蛋糕要求各有1~N个草莓(共N*(N+1)/2个草莓)。
求满足相邻两块蛋糕草莓数和为平方数的最小的N。
思路:DFS
def solve1(total, prev, used, squares):
if total == len(used):
if prev+1 in squares:
return True
else:
for i in range(2, total+1):
if (i+prev) in squares and i not in used:
if solve1(total, i, used+[i], squares):
return True
return False
def test1():
start = time.perf_counter()
n = 2
while True:
squares = { i*i for i in range(2, int(n ** 2)) }
if solve1(n, 1, [1], squares):
print(n)
break
n += 1
end = time.perf_counter()
print('solve1 time cost: ', end - start)
32
solve1 time cost: 0.3892214992083609
优化判断是否已使用的方式:
def solve2(total, left, prev, used, squares):
if left == 0:
if prev+1 in squares:
return True
else:
for i in range(2, total+1):
if not used[i] and (i+prev) in squares:
used[i] = True
if solve2(total, left-1, i, used, squares):
return True
used[i] = False
return False
def test2():
start = time.perf_counter()
n = 2
while True:
squares = { i*i for i in range(2, int(n ** 2)) }
used = [False] * (n+1)
used[1] = True
if solve2(n, n-1, 1, used, squares):
print(n)
break
n += 1
end = time.perf_counter()
print('solve2 time cost: ', end - start)
32
solve2 time cost: 0.29970831889659166
求出切蛋糕的顺序:
def solve3(total, prev, used, squares):
if total == len(used):
if prev+1 in squares:
print(used)
return True
else:
for i in range(2, total+1):
if (i+prev) in squares and i not in used:
if solve3(total, i, used+[i], squares):
return True
return False
def test3():
start = time.perf_counter()
n = 2
while True:
squares = { i*i for i in range(2, int(n ** 2)) }
ans = solve3(n, 1, [1], squares)
if ans:
print(ans)
break
n += 1
end = time.perf_counter()
print('solve3 time cost: ', end - start)
[1, 8, 28, 21, 4, 32, 17, 19, 30, 6, 3, 13, 12, 24, 25, 11, 5, 31, 18, 7, 29, 20, 16, 9, 27, 22, 14, 2, 23, 26, 10, 15]
True
solve3 time cost: 0.40476082591339946
seq = []
def solve4(total, left, prev, used, squares):
if left == 0:
if prev+1 in squares:
return True
else:
for i in range(2, total+1):
if not used[i] and (i+prev) in squares:
used[i] = True
seq.append(i)
if solve4(total, left-1, i, used, squares):
return True
used[i] = False
seq.pop()
return False
def test4():
start = time.perf_counter()
n = 2
while True:
squares = { i*i for i in range(2, int(n ** 2)) }
used = [False] * (n+1)
used[1] = True
global seq
seq = [1]
if solve4(n, n-1, 1, used, squares):
print(n)
break
n += 1
end = time.perf_counter()
print(seq)
print('solve2 time cost: ', end - start)
[1, 8, 28, 21, 4, 32, 17, 19, 30, 6, 3, 13, 12, 24, 25, 11, 5, 31, 18, 7, 29, 20, 16, 9, 27, 22, 14, 2, 23, 26, 10, 15]
solve2 time cost: 0.3256980921141803
假设有相同约数(不包括1)的数字互为好友,从1~N任选一个合数,从它开始,要经历几层好友才能和其他所有数产生联系。
求从1~N选7个合数时,最多经过6层就可以与其他所有数产生联系的最小的N。
思路:从1~N选7个合数,最多经过6层,可知我们要找的是由2个数相乘得到的数字组合。
设a~h互质,当7个合数为
a*b, b*c, c*d, d*e, e*f, f*g, g*h时,a经过6次可与g产生联系,5次可与f产生联系,4次可与e产生联系….
b经过1次可与a,c产生联系,5次可与g产生联系…
而所要求的答案是a*b, b*c … g*h中的最大值。
而当7个数为
a*a, a*b, b*c, c*d, d*e, e*f, f*f时得到的结果更小。
from itertools import permutations
def is_prime(n):
if n <= 1:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
def solve(n):
primes = []
x = 2
while len(primes) < n:
if is_prime(x):
primes += [x]
x += 1
ans = (primes[-1] * primes[-1], [])
for perm in permutations(primes):
friends = [perm[0] * perm[0], perm[-1] * perm[-1]] + [perm[i]*perm[i-1] for i in range(1, len(perm))]
if max(friends) < ans[0]:
ans = max(friends), friends
return ans
ans = solve(6)
print(ans)
(55, [4, 49, 26, 39, 33, 55, 35])
1 14 14 4
11 7 6 9
8 10 10 5
13 2 3 15
上面叫“受难立面”,横纵对角线相加,和都为33,
如果从这些数字中任选任意个数,和为33的有310种,若限定4个数,则有88种。
求”和相同的组合“ 种数最多的和。
思路:组合
import itertools
import time
square = [1, 14, 14, 4, 11, 7, 6, 9,
8, 10, 10, 5, 13, 2, 3, 15]
def solve1(square):
d = {}
for size in range(1, len(square)):
combs = itertools.combinations(square, size)
for t in combs:
s = sum(t)
if s in d:
d[s] += 1
else:
d[s] = 1
m = max(d, key=d.get)
print(m, d[m])
s = time.perf_counter()
solve1(square)
e = time.perf_counter()
print("time cost: ", e-s)
66 1364
time cost: 0.018467612098902464
思路:DP
def solve2(square):
all = sum(square)
dp = [0] * (all + 1)
dp[0] = 1
for n in square:
for i in range(all - n, -1, -1):
dp[i + n] += dp[i]
m = max(dp)
print(dp.index(m), m)
s = time.perf_counter()
solve2(square)
e = time.perf_counter()
print("time cost: ", e-s)
66 1364
time cost: 0.00016872398555278778