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

48 篇文章 0 订阅

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值