美文网首页
算法思维训练趣味任务(十四) 适龄高中阶段

算法思维训练趣味任务(十四) 适龄高中阶段

作者: Python_Camp | 来源:发表于2022-05-28 15:47 被阅读0次

在金盆洗手游戏中,有两个玩家,A和B,金盆排列成一排,每个金盆里都有一些金币。玩家可以看到每个金罐里有多少金币,每个玩家可以交替轮换,玩家可以从队伍的任何一端挑选一个金罐。最后拥有较多金币的玩家就是赢家。目标是 "最大化 "A收集的金币数量,假设B也是 "最佳 "发挥,A开始游戏。

比如说 For example

                      Player    Opponent
 
4, 6, 2, 3              3
4, 6, 2                            4
6, 2                    6
2                                  2

                     9 coins    6 coins
                 9枚硬币 6枚硬币
                           玩家 对手
 
6, 1, 4, 9, 8, 5        6
1, 4, 9, 8, 5                      5
1, 4, 9, 8              8
1, 4, 9                            9
1, 4                    4
1                                  1
                     18 coins   15 coins
                     18枚硬币 15枚硬币

练习这个问题

我们的想法是,在明知对手是最优策略的情况下,找到一个使玩家获胜的最优策略。玩家对硬币[i...j]有两个选择,其中i和j分别表示行的前部和后部。

1. 如果玩家选择了前面的锅i,对手就只能从[i+1, j]中选择。

如果对手选择了前锅i+1,则重复选择[i+2, j]。
如果对手选择了后锅j,则重复进行[i+1, j-1]。
2. 如果棋手选择了后锅j,那么对手就只能从[i, j-1]中选择。

如果对手选择了前锅i,则递归为[i+1, j-1]。
如果对手选择了后边的锅j-1,则重复选择[i, j-2]。

由于对手是以最优化的方式进行游戏,他将试图使玩家的点数最小化,即对手将做出使玩家的硬币最少的选择。因此,我们可以递归地将问题定义为。

伪代码

                         | coin[i] (如果i = j)
 optimalStrategy(i, j) = | max(coin[i], coin[j]) (if i + 1 = j)
                         | max (coin[i] + min(optimalStrategy(coin, i + 2, j),
                                        optimalStrategy(coin, i + 1, j - 1))。
                          coin[j] + min(optimalStrategy(coin, i + 1, j - 1),
                                           optimalStrategy(coin, i, j - 2)))

# Recursive function to maximize the number of coins collected by a player,
# assuming that the opponent also plays optimally
def findMaxCoins(coin, i, j):
 
    # base case: one pot left, only one choice possible
    if i == j:
        return coin[i]
 
    # if we are left with only two pots, choose one with maximum coins
    if i + 1 == j:
        return max(coin[i], coin[j])
 
    # if a player chooses front pot `i`, the opponent is left to choose from [i+1, j]
    # 1. If the opponent chooses front pot `i+1`, recur for [i+2, j]
    # 2. If the opponent chooses rear pot `j`, recur for [i+1, j-1]
 
    start = coin[i] + min(findMaxCoins(coin, i + 2, j),
                        findMaxCoins(coin, i + 1, j - 1))
 
    # if a player chooses rear pot `j`, the opponent is left to choose from [i, j-1]
    # 1. If the opponent chooses front pot `i`, recur for [i+1, j-1]
    # 2. If the opponent chooses rear pot `j-1`, recur for [i, j-2]
 
    end = coin[j] + min(findMaxCoins(coin, i + 1, j - 1),
                        findMaxCoins(coin, i, j - 2))
 
    # return the maximum of two choices
    return max(start, end)
 
 
# Pots of gold game using dynamic programming
if __name__ == '__main__':
 
    # pots of gold (even number) arranged in a line
    coin = [4, 6, 2, 3]
 
    print('The maximum coins collected by player is',
        findMaxCoins(coin, 0, len(coin) - 1))

以下是上述方法的Python实现。

集装箱码头摆放货柜
一排摆放的货柜,每个货柜的编号头字母是大写的字母 A->Z
现在你要编写指令,搬运机器人调整货柜的位置,目标是货柜编号按照字母表的顺序摆放。你考虑到节能减排的需求,尽量做到搬运的次数最少。

例如:
'ECADBF',字符串格式表示货柜摆放的位置,目标是调整为 'ABCDEF'
最佳的搬运方案是:

D, F不动 !

1、E和B 交换位置后,'BCADEF'
2、A和B 交换位置后,'ACBDEF'
2、C和B 交换位置后,'ABCDEF'
共搬运 3 次即可完成!

def swapcaseUP(start):
    s = start
    swap = {}
    cunt = 1
    while s != sorted(s):
        sorder = sorted(start)
  
        for i,c in enumerate(s):
            if i == sorder.index(c):pass
            else:
                t = sorder.index(c)
                s[t],s[i] = s[i], s[t]  # 不能用 c !!1
                swap[cunt] = f"{chr(s[t])} <-> {chr(s[i])}"
                cunt += 1

    return len(swap),swap,''.join([chr(i) for i in s])

输出结果:

测试用例 >>>

start = 'ABCDEFGHIJK'
print(f' {start}:',swapcaseUP(start))
ABCDEFGHIJK: (0, {}, 'ABCDEFGHIJK')


start = 'ABCDEFGHIJK'[::-1]
print(f' {start}:',swapcaseUP(start))
 KJIHGFEDCBA: (5, {1: 'K <-> A', 2: 'J <-> B', 3: 'I <-> C', 4: 'H <-> D', 5: 'G <-> E'}, 'ABCDEFGHIJK')

start = 'FCABDE'
print(f'{start}:',swapcaseUP(start))
FCABDE: (5, {1: 'F <-> E', 2: 'C <-> A', 3: 'B <-> A', 4: 'D <-> A', 5: 'E <-> A'}, 'ABCDEF')

start = 'FCADBE'
print(f' {start}:',swapcaseUP(start))
 FCADBE: (4, {1: 'F <-> E', 2: 'C <-> A', 3: 'B <-> A', 4: 'E <-> A'}, 'ABCDEF')


start = 'ECADBF'
print(f' {start}:',swapcaseUP(start))
 ECADBF: (3, {1: 'E <-> B', 2: 'C <-> A', 3: 'B <-> A'}, 'ABCDEF')


start = 'FEDCBA'
print(f' {start}:',swapcaseUP(start))
 FEDCBA: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEF')


start = 'FEDCBAGH'
print(f' {start}:',swapcaseUP(start))
 FEDCBAGH: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEFGH')

start = 'FEDCBAGHIJ'
print(f'{start}:',swapcaseUP(start))
FEDCBAGHIJ: (3, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C'}, 'ABCDEFGHIJ')


start = 'FEDCBAGHJI'
print(f' {start}:',swapcaseUP(start))
 FEDCBAGHJI: (4, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C', 4: 'J <-> I'}, 'ABCDEFGHIJ')

start = 'FEDCBAHGJI'
print(f' {start}:',swapcaseUP(start))
 FEDCBAHGJI: (5, {1: 'F <-> A', 2: 'E <-> B', 3: 'D <-> C', 4: 'H <-> G', 5: 'J <-> I'}, 'ABCDEFGHIJ')
start = 'FAJIHGKEDCB'
print(f' {start}:',swapcaseUP(start))
 FAJIHGKEDCB: (7, {1: 'F <-> G', 2: 'A <-> G', 3: 'J <-> C', 4: 'I <-> D', 5: 'H <-> E', 6: 'K <-> B', 7: 'G <-> B'}, 'ABCDEFGHIJK')
start = 'DFAECIGBJH'
print(f' {start}:',swapcaseUP(start))
DFAECIGBJH: (7, {1: 'D <-> E', 2: 'F <-> I', 3: 'A <-> E', 4: 'C <-> E', 5: 'B <-> I', 6: 'J <-> H', 7: 'I <-> H'}, 'ABCDEFGHIJ')
start = 'CDABEKIHGJF'
print(f' {start}:',swapcaseUP(start))
 CDABEKIHGJF: (4, {1: 'C <-> A', 2: 'D <-> B', 3: 'K <-> F', 4: 'I <-> G'}, 'ABCDEFGHIJK')

团购水果的分配任务

自发组织团购了一车新鲜水果,预约的货车运载量是5千公斤左右。在疫情期间,我将分给小区的100多家。数量需求不尽不同,还好经验丰富的供货商考虑到水果发放要尽量便捷,提供大小不同的包装袋密封好,到小区后不必打开包装,通过适当的几种包装的组合能够凑出订购的数量。这样做以来,不仅省去每一家称重的时间,也极大地缩短人员聚集的时间。

你作为程序员更容易想到一个办法,建议水果供货商将水果分为固定的几种若干斤的重量的包装,最终能保证每家来领水果的时候,无论订购了多少斤,一定能找到组合出每一家的水果需求量。同时不会剩下,如何做到看起来近乎不可能的事情,希望你能给出各种包装的数量。

策略

首先要准确量出 1 -> n,且 sum(1+ ... +n) <= 5000 less or above

找到某种规律组合,能够凑出上述任意数量的水果

每一种包装的数量确定

def evensplit(n):
    total = 1
    i = 1
    bench = []
    while total <= n:
       bench.append(i)
       total += i
       i += 1
    maxwgt = len(bin(bench[-1])[2:])
    bins = [2**i for i in range(maxwgt+1)][::-1]
    weights = dict(zip(bins,len(bins)*[0]))

    for n in bench[::-1]:
        for k,v in weights.items():
            weights[k] += n//k
            n = n % k
    return total,bins,weights

n = 5000
print('total ',evensplit(n))

total  (5051, [128, 64, 32, 16, 8, 4, 2, 1], {128: 0, 64: 37, 32: 37, 16: 48, 8: 48, 4: 49, 2: 50, 1: 50})

领取水果的分配包装

输入参数是领取的重量 n

def pick(n):
    b = bin(n)[2:][::-1]
    ans = [2**int(i) for i,e in enumerate(b) if e == '1']
    return [f"{i} kg 一袋" for i in ans]

n = 10
print(pick(n))

n = 17
print(pick(n))

n = 50
print(pick(n))

n = 100
print(pick(n))


['2 kg 一袋', '8 kg 一袋']
['1 kg 一袋', '16 kg 一袋']
['2 kg 一袋', '16 kg 一袋', '32 kg 一袋']
['4 kg 一袋', '32 kg 一袋', '64 kg 一袋']

启发商家的包装从小到大可以采用 1,2,4,8,... ..kg的包装

当然上述例子假设每一家的需求数量都不同时,而且一整车货载重固定5000kg的约束条件下,批发水果的包装大小的组合数量分布。

进一步看实际遇到有顾客订购相同数量的需求,现在解决起来变得容易。只需稍微修改代码:
例如:有 5 人 购买 20kg,有 7 人购买 30kg
我们需要在总量不变的情况下,调整特定包装的数量。

#1 首先看要增加哪些包装
s = [(5,20),(7,30)]

def picknum(n):
    b = bin(n)[2:][::-1]
    ans = [2**int(i) for i,e in enumerate(b) if e == '1']
    return ans

# 保持总量的情况下,减少哪些包装
def adjust(s,n):
    n = n - sum([x*y for x,y in s])
    wgt = evensplit(n)[2]
    t = [(w[0],picknum(w[1])) for w in s]
    for m in t:
        for k,v in wgt.items():
            for i in m[1]:
                if i == k:
                    wgt[k] += m[0]

    return wgt

s,n = [(5,20),(7,30)],5000
print(adjust(s,n))

{128: 0, 64: 34, 32: 34, 16: 60, 8: 55, 4: 60, 2: 55, 1: 49}
对比原包装方案:
{128: 0, 64: 37, 32: 37, 16: 48, 8: 48, 4: 49, 2: 50, 1: 50})

结论是:

64kg的包装减少 3 袋
32kg的包装增加 3 袋
16kg的包装增加 2 袋
8kg的包装增加 7 袋
4kg的包装增加 11 袋
2kg的包装增加 5 袋
1kg的包装减少 1 袋

春节抢红包

我在微信群里亲朋好友的孩子发红包,因为每个孩子都很在意收的红包会不会比其他孩子少,于是我选择没有选择随机红包,而是平均等分红包。

而我又不想有剩下的钱而退回来。考虑到有孩子会错过抢红包。我计算了以下最少要往红包里转账多少钱,才能保证:

无论最后有多少孩子来参与抢红包,都能保证每个孩子都能得到相同的金额,同时我又不会剩下的部分钱退回?(请放心保证至少有一个孩子参加抢红包)。

输入参数 num -> 微信群的人数

def redPacket(num):
    n = 1
    for i in range(num):
        init = n
        while n % (i+1):
            n += init
    return n

amount_of_kids = 5
print(f'{amount_of_kids}',redPacket(amount_of_kids))
5 60

amount_of_kids = 7
print(f'{amount_of_kids}',redPacket(amount_of_kids))
7 420

amount_of_kids = 10
print(f'{amount_of_kids}',redPacket(amount_of_kids))
10 2520

结论:

如果邀请5人,无论1-5之间的人数,至少要准备 60 元保证每个抢到相同的数量;

如果邀请10人,无论来1-10之间的人数,至少要准备 420 元保证抢到相同的数量;

如果邀请10人,无论来1-10之间的人数,至少要准备 2520 元保证抢到相同的数量;

相关文章

网友评论

      本文标题:算法思维训练趣味任务(十四) 适龄高中阶段

      本文链接:https://www.haomeiwen.com/subject/vwugprtx.html