注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用Ruby实现,最近在学Python,随便找点东西写写当做练习,准备改成Python3实现,顺便增加一些自己的理解。
21.异或运算三角形
1
1 1
1 0 1
1 1 1 1
…
帕斯卡三角形计算法则是 “某个数值是其左上角的数和右上角的数之和”,用异或代替和,自上而下,求第2018个0出现在哪一层。如第1个0在第三层,第2,3,4个在第5层。
思路:从上往下计算
def solve1(n):
count, ans = 0, 1
row = [1]
while count < n:
next_row = [1]
for i in range(len(row) - 1):
x = row[i] ^ row[i+1]
next_row.append(x)
if x == 0:
count += 1
next_row.append(1)
ans += 1
row = next_row
return ans
print(solve1(2018))
75
思路:数学方法,
第6层: 110011
左移1位:1100110
异或:110011 oxr 1100110 = 1010101(第7层)
def solve2(n):
count, ans, row = 0, 1, 1
while count < n:
row ^= (row << 1)
count += format(row, 'b').count("0")
ans += 1
return ans
print(solve2(2018))
75
22.不缠绕的纸杯电话
小的时候常玩用绳子连接纸杯制作纸杯电话,假设几个小朋友,以相同间隔围成圆周,结对用纸杯电话通话,要求绳子不交叉,求有16个小朋友时,一共有多少种结对方法。
如有6个小朋友时,结对方法可以有下面几种情况:
思路:为了使绳子不交叉,可以从任意位置将小朋友划分成两部分,各部分各自计算结对方式。
f(x) = sum(f(i) * f(i-j-1) for j < i)
def solve(n):
dp = [0] * (n//2 + 1)
dp[0] = 1
for i in range(1, n//2 + 1):
dp[i] = 0;
for j in range(0, i):
dp[i] += dp[j] * dp[i - j - 1]
return dp[n//2]
print(solve(16))
1430
23.二十一点通吃
二十一点游戏每次最少下注1枚硬币,赢了地2枚硬币,输了硬币被收走。
假设开始有1枚硬币,每回合下注1枚,4回合后还能剩余硬币的变化情况如图:
问:开始拥有10枚硬币,24回合后还能剩余硬币变化情况有多少种?
思路:递归
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(func.__name__, end - start)
return result
return wrapper
def solve1(round, coin):
if coin == 0:
return 0
if round == 0:
return 1
win = solve1(round-1, coin+1)
lost = solve1(round-1, coin-1)
return win+lost
@timethis
def test1():
print(solve1(24, 10))
16051010
test1 4.356126308441162
优化:带记忆的递归
def solve2(d, round, coin):
if (round, coin) in d:
return d[(round, coin)]
if coin == 0:
return 0
if round == 0:
return 1
d[(round, coin)] = solve2(d, round-1, coin-1) + solve2(d, round-1, coin+1)
return d[(round, coin)]
@timethis
def test2():
d = {}
print(solve2(d, 24, 10))
16051010
test2 0.00017261505126953125
24.完美的三振出局
对喜爱棒球的少年而言,“三振出局”A( 图 10 )是一定要试一次的。这是一个在本垒上放置 9 个靶子,击打投手投来的球,命中靶子的游戏。据说这可以磨练球手的控制力。现在来思考一下这 9 个靶子的击打顺序。假设除了高亮的 5 号靶子以外,如果 1 个靶子有相邻的靶子,则可以一次性把这 2 个靶子都击落。譬如,如图 11 所示,假设 1 号、6 号、9 号已经
被 击 落 了, 那 么 接 下 来, 对 于“2 和3”“4 和 7”“7 和 8”这 3 组靶子,我们就可以分别一次性击落了。
假设每次击球都命中,问9个靶子击落顺序有多少种?
思路:每次击落方式有两种,1块和2块
首先找出这些方式,如2块的时候包括(1, 2), (2, 3), (7, 8)…
然后递归,需要保证已击落的不能再次击落。
def solve1(d, board):
if str(board) in d:
return d[str(board)]
count = 0
for i in board:
new_board = []
for j in board:
if len(i & j) == 0:
new_board.append(j)
count += solve1(d, new_board)
d[str(board)] = count
return count
@timethis
def test1():
board = [ {1, 2}, {2, 3}, {7, 8}, {8, 9}, {1, 4}, {4, 7}, {3, 6}, {6, 9} ]
for i in range(1, 10):
board.append({i})
d = {}
d[str([])] = 1
ans = solve1(d, board)
print(ans)
test1()
798000
test1 0.02419114112854004
更pythonic的写法:
def solve2(d, board):
if str(board) in d:
return d[str(board)]
count = 0
for i in board:
count += solve2(d, [j for j in board if len(i & j) == 0])
d[str(board)] = count
return count
@timethis
def test2():
board = [ {1, 2}, {2, 3}, {7, 8}, {8, 9}, {1, 4}, {4, 7}, {3, 6}, {6, 9} ]
for i in range(1, 10):
board.append({i})
d = {}
d[str([])] = 1
ans = solve2(d, board)
print(ans)
test2()
798000
test2 0.02462148666381836
25.鞋带的时髦系法
即便系得很紧,鞋带有时候还是免不了会松掉。运动鞋的鞋带有很多时髦的系法。下面看看这些系法里,鞋带是如何穿过一个又一个鞋带孔的。如 图 12 所示的这几种依次穿过 12 个鞋带孔的系法很有名(这里不考虑鞋带穿过鞋带孔时是自外而内还是自内而外)。
这里指定鞋带最终打结固定的位置如 图 12 中的前两种系法所示,即固定在最上方(靠近脚腕)的鞋带孔上,并交错使用左右的鞋带孔。
求鞋带交叉点最多时的交叉点个数。譬如 图 12 左侧的系法是 5 个,正中间的这种系法是 9 个。
思路:枚举穿孔顺序,求交点个数
def solve1(n):
ans = 0
nums = [i for i in range(1, n+1)]
for perm_left in itertools.permutations(nums):
for perm_right in itertools.permutations(nums):
path = []
l, r = 0, perm_right[0]
for i in range(n-1):
path.append((l, r))
l = perm_left[i]
path.append((l, r))
r = perm_right[i+1]
path.append((l, 0))
length = len(path)
count = 0
for i in range(length):
for j in range(i+1, length):
if (path[i][0] > path[j][0] and path[i][1] < path[j][1]) or \
(path[i][0] < path[j][0] and path[i][1] > path[j][1]):
count += 1
if ans < count:
ans = count
return ans
@timethis
def test1():
print(solve1(6))
test1()
45
test1 cost time: 8.11267375946045
用乘法代替比较:
def solve2(n):
ans = 0
nums = [i for i in range(1, n+1)]
for perm_left in itertools.permutations(nums):
for perm_right in itertools.permutations(nums):
path = []
l, r = 0, perm_right[0]
for i in range(n-1):
path.append((l, r))
l = perm_left[i]
path.append((l, r))
r = perm_right[i+1]
path.append((l, 0))
length = len(path)
count = 0
for i in range(length):
for j in range(i+1, length):
if (path[i][0] - path[j][0]) * (path[i][1] - path[j][1]) < 0:
count += 1
if ans < count:
ans = count
return ans
@timethis
def test2():
print(solve2(6))
test2()
45
test2 cost time: 6.878921747207642