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

注:以下题目来自《程序员的算法趣题》– [日]增井敏克著,原书解法主要用Ruby实现,最近在学Python,随便找点东西写写当做练习,准备改成Python3实现,顺便增加一些自己的理解。

1.回文数

求十进制、二进制、八进制表示都是回文数的数字中,大于十进制数10的最小值。

思路:转成字符串,比较是否跟自己反转后相同

num = 11
while True:
    s10 = str(num)
    s2  = str(bin(num))[2:]
    s8  = str(oct(num))[2:]
    if s10 == s10[::-1] and s2 == s2[::-1] and s8 == s8[::-1]:
        break
    num += 2

print(num, bin(num), oct(num))

585 0b1001001001 0o1111
python转成2进制会在前面加0b,八进制会在前面加0o,所以s2, s8都取了[2 :]切片。

使用format:

num = 11
while True:
    s10 = str(num)
    s2  = format(num, 'b')
    s8  = format(num, 'o')
    if s10 == s10[::-1] and s2 == s2[::-1] and s8 == s8[::-1]:
        break
    num += 2

print(num, bin(num), oct(num))

585 0b1001001001 0o1111

2.四则运算

一个数,可以在数字间插入运算符+, -, *, / 也可以不插入,求至少插入一个运算符且满足结果为原来数字逆序的数,如:
351 -> 3*51 = 153
621 -> 6*21 = 126

对于三位数:

ops = ['+', '-', '*', '/', '']

def solve3():
    for i in range(100, 1000):
        s = str(i)
        for op1 in ops:
            for op2 in ops:
                exp = s[0] + op1 + s[1] + op2 + s[2]
                if len(exp) > len(s):
                    try:
                        if s[::-1] == str(eval(exp)):
                            ans.append(exp + '=' + s[::-1])
                    except:
                        pass

ans = []
solve3()
print(ans)

[‘3*51=153’, ‘6*21=126’, ‘8*86=688’]

四位数:

ops = ['+', '-', '*', '/', '']

def solve4():
    for i in range(1000, 10000):
        s = str(i)
        for op1 in ops:
            for op2 in ops:
                for op3 in ops:
                    exp = s[0] + op1 + s[1] + op2 + s[2] + op3 + s[3]
                    if len(exp) > len(s):
                        try:
                            if s[::-1] == str(eval(exp)):
                                ans.append(exp + '=' + s[::-1])
                        except:
                            pass

ans = []
solve4()
print(ans)

[‘5*9*31=1395’]

如何避免靠嵌套for循环呢?

ops = ['+', '-', '*', '/', '']

def dfs(num, pos, exp):
    s = str(num)
    if pos == len(s):
        if len(exp) > len(s):
            try:
                if s[::-1] == str(eval(exp)):
                    ans.append(exp + '=' + s[::-1])
            except:
                pass
        return

    for op in ops:
        dfs(s, pos+1, exp+op+s[pos])

ans = []
for i in range(10, 6000):
    if dfs(i, 1, str(i)[0]):
        ans.append(i)
print(ans)

[‘3*51=153’, ‘6*21=126’, ‘8*86=688’, ‘5*9*31=1395’]

另一种写法:

ops = ['+', '-', '*', '/', '']

def dfs2(num, pos, exp):
    s = str(num)
    if pos == len(s)-1:
        if len(exp) >= len(s):
            exp = exp + s[-1]
            try:
                if s[::-1] == str(eval(exp)):
                    ans.append(exp + '=' + s[::-1])
            except:
                pass
        return

    for op in ops:
        dfs2(s, pos+1, exp+s[pos]+op)

ans = []
for i in range(10, 6000):
    if dfs2(i, 0, ''):
        ans.append(i)
print(ans)

[‘3*51=153’, ‘6*21=126’, ‘8*86=688’, ‘5*9*31=1395’]

3.翻牌

100张牌,写着1~100,顺序排列,开始背面朝上,按如下顺序翻牌:
1)从第2张开始,隔1张牌翻牌;
2)从第3张开始,隔2张牌翻牌;
3)从第4张开始,隔3张牌翻牌;
….
直到没有可翻动的牌为止,求最后背面朝上的牌的数字

思路:模拟,用一个数组记录当前朝上还是朝下,模拟整个翻牌过程

def solve(n):
    cards = [False] * n
    for i in range(2, n+1):
        for j in range(i-1, n, i):
            cards[j]  = not cards[j]

    ans = []
    for i in range(n):
        if not cards[i]:
            ans.append(i+1)

    return ans

ans = solve(100)
print(ans)

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

思路2:翻牌规律:
2, 4, 6, 8, 10, 12….
3, 6, 9, 12…
4, 8, 12…
5, 10, 15…
6, 12…

12…
12在2, 3, 4, 6, 12各被翻动一次,翻了奇数次,所以最后正面朝上
即每个数字都是在它的约数被翻转,模拟这个过程:

def solve2(n):
    if n < 1:
        return []

    ans = [1]
    for i in range(2, n+1):
        flag = False
        for j in range(2, i):
            if i % j == 0:
                flag = not flag

        if flag:
            ans.append(i)

    return ans

ans = solve2(100)
print(ans)

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

思路3:数字的规律,可以发现结果都是平方数,直接算平方数:

def solve3(n):
    ans = []
    i = 1
    while i*i <= n:
        ans.append(i*i)
        i += 1

    return ans

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

4.切木棒

把n cm长的木棒切为1cm的小段,一根木棒只能一个人切,当切为两段时可由2人同时切,切为3段时可由3人同时切。
求长为n的木棒,m个人同时切,最少切几次?
如n=8, m=3,
8 -> 1人切,… 1次
4, 4 -> 2 人切,… 2次
2, 2, 2, 2 -> 3 人切, … 3次
1, 1, 1, 1, 1, 1, 2 -> 1 人切, … 4次
1, 1, 1, 1, 1, 1, 1, 1
求n=20, m=3时最少切几次? n = 100, m= 5 时最少切几次?

思路1:递归

def solve1(n, m, current):
    if current >= n:
        return 0
    if current <= m:
        return 1 + solve1(n, m, current*2)
    else:
        return 1 + solve1(n, m, current + m)

print(solve1(20, 3, 1))
print(solve1(100, 5, 1))

8
22

思路2:递推

def solve2(n, m):
    ans, current = 0, 1
    while current < n:
        current += current if current < m else m
        ans += 1
    return ans

print(solve2(20, 3))
print(solve2(100, 5))

8
22

5.兑换硬币

一个机器可以用纸币兑换硬币,硬币有假设10,50,100,500四种,且机器最多兑换出15个硬币,比如1000纸币不能兑换出100个面值10的硬币。求兑换1000面值的纸币会有多少组合?(注日元)

思路1:多重循环

def solve1(n):
    ans = 0
    for c500 in range(0, n//500 + 1):
        for c100 in range(0, n//100 + 1):
            for c50 in range(0, n//50 + 1):
                for c10 in range(0, n//10 + 1):
                    if c500 + c100 + c50 + c10 <= 15:
                        if 500*c500 + 100*c100 + 50*c50 + 10*c10 == n:
                            ans += 1
    return ans

print(solve1(1000))

20

思路2:使用python内置的组合

import itertools
def solve2(n):
    coins = [500, 100, 50, 10]
    ans = 0
    for i in range(2, 16):
        for comb in itertools.combinations_with_replacement(coins, i):
            if sum(comb) == n:
                ans += 1
    return ans

print(solve2(1000))

20

思路3:递归

def solve3(target, coins, max_num):
    def dfs(target, index, left):
        if index == len(coins):
            if target == 0:
                ans[0] += 1
        else:
            for i in range(0, target // coins[index] + 1):
                if i <= left:
                    dfs(target - coins[index]*i, index+1, left-i)

    ans = [0]
    dfs(target, 0, max_num)
    return ans[0]

print(solve3(1000, [500, 100, 50, 10], 15))

20

点赞