程序员的算法趣题 python3 - (5)

注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用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

猜你喜欢

转载自blog.csdn.net/guzhou_diaoke/article/details/80873951