用 Python 解决难题

Posted

技术标签:

【中文标题】用 Python 解决难题【英文标题】:Solving Puzzle in Python 【发布时间】:2012-05-10 09:51:54 【问题描述】:

我有一个难题,我想用 Python 解决它。

谜题:

一位商人有一个 40 公斤的重量,他在他的商店中使用。有一次,掉了 从他的手中,被分成了4块。但令人惊讶的是,现在他 可以称重 1 公斤到 40 公斤之间的任何重量 这 4 件。

所以问题是,这 4 件的重量是多少?

现在我想用 Python 解决这个问题。

我从这个谜题中得到的唯一限制是 4 块的总和是 40。这样我就可以过滤所有总和为 40 的 4 个值的集合。

import itertools as it

weight = 40
full = range(1,41)
comb = [x for x in it.combinations(full,4) if sum(x)==40]

length of comb = 297

现在我需要检查comb中的每一组值,并尝试所有的操作组合。

例如如果(a,b,c,d)comb中的第一组值,我需要检查a,b,c,d,a+b,a-b, .................a+b+c-d,a-b+c+d........等等。

我尝试了很多,但我被困在这个阶段,即如何检查所有这些计算组合到每组 4 个值。

问题:

1) 我想我需要列出[a,b,c,d] and [+,-] 的所有可能组合。

2) 有没有人有更好的主意并告诉我如何从这里继续前进?

另外,我想完全不需要任何外部库的帮助,只需要使用 python 的标准库。

编辑:抱歉信息迟到了。它的答案是 (1,3,9,27),这是我几年前发现的。我已经检查并验证了答案。

编辑:目前,fraxel 的答案与time = 0.16 ms 完美搭配。更好更快的方法总是受欢迎的。

问候

方舟

【问题讨论】:

这个谜题比这更棘手;我不确定您是否可以轻松地暴力破解它。诀窍是,为了测量某些重量,他可能需要在秤的两侧添加重量的碎片。考虑一个更简单的版本:将一个 4 公斤重的砝码分成 2 块,可以测量到 4 公斤以下的任何重量。答案是 1 公斤件和 3 公斤件。要测量 2 公斤,您必须在秤的每一侧放一块。 @JacobM 有最好的方法:从一个更简单的问题开始,看看你是否找不到可以解决更复杂问题的模式。另外,请注意,除非您确定每个重量都是独一无二的,否则组合不会给您想要的。 (要看到这一点,请尝试将 weight 更改为 10 并将 full 更改为 range(1,10)。更容易玩弄它的功能。) @JacobM...是的..当然..即问题。您可以在秤的两侧放置砝码以获得所需的重量。即我在问题中提到了negative sign。即a-b, a-b+c-d ....minus 表示重量放在其他秤上。我想我必须有问题地解释它。感谢您的通知。 阅读问题 andrew,他想用 Python 解决它,我们是谁来阻止他! 好的,我删除了我的评论,以防它是一个不需要的“剧透”(对不起)。如果您想知道如何写下答案,请给我发电子邮件。 【参考方案1】:

之前的演练答案:

我们知道a*A + b*B + c*C + d*D = x 代表介于 0 和 40 之间的所有 x,而 a, b, c, d 仅限于 -1, 0, 1。显然A + B + C + D = 40。下一个案例是x = 39,所以显然最小的移动是删除一个元素(这是唯一可能导致成功平衡 39 的移动):

A + B + C = 39,所以D = 1,根据需要。

下一个:

A + B + C - D = 38

下一个:

A + B + D = 37,所以C = 3

然后:

A + B = 36

然后:

A + B - D = 35

A + B - C + D = 34

A + B - C = 33

A + B - C - D = 32

A + C + D = 31,所以A = 9

因此B = 27

所以权重是1, 3, 9, 27

实际上,这可以从它们都必须是 3 的倍数这一事实中立即推断出来。

有趣的更新:

所以这里有一些 Python 代码,可以为任何跨越空间的掉落重量找到最小的重量集:

def find_weights(W):
    weights = []
    i = 0
    while sum(weights) < W:
        weights.append(3 ** i)
        i += 1
    weights.pop()
    weights.append(W - sum(weights))
    return weights

print find_weights(40)
#output:
[1, 3, 9, 27]

为了进一步说明这一解释,可以将问题视为跨越数字空间[0, 40] 的最小权重数。很明显,每个重量可以做的事情的数量是三元/三元(增加重量,去除重量,把重量放在另一边)。因此,如果我们按降序编写(未知)权重(A, B, C, D),我们的动作可以总结为:

    ABCD:   Ternary:
40: ++++     0000
39: +++0     0001
38: +++-     0002
37: ++0+     0010
36: ++00     0011
35: ++0-     0012
34: ++-+     0020
33: ++-0     0021
32: ++--     0022
31: +0++     0100
etc.

我把从 0 到 9 的三进制数放在旁边,以说明我们实际上是在一个三进制数系统中(以 3 为底)。我们的解决方案总是可以写成:

3**0 + 3**1 +3**2 +...+ 3**N >= Weight

对于这个成立的最小 N。最小的解决方案总是这种形式。

此外,我们可以轻松解决大权重问题并找到跨越空间的最小块数:

一个人掉下一个已知重量 W,它碎成碎片。他的新砝码允许他称重最大为 W 的任何重量。有多少个砝码,它们是什么?

#what if the dropped weight was a million Kg:
print find_weights(1000000)
#output:
[1, 3, 9, 27, 81, 243, 729, 2187, 6561, 19683, 59049, 177147, 531441, 202839]

尝试对大重量和未知数量的碎片使用排列!

【讨论】:

A + B + C = 39,因此 D = 1,这是必要的。我不太确定。如果你有 14 和 15 的权重,你可以使用这 2 个来做 1,每边一个,因为 15-14 = 1。所以不确定必要性是正确的词。 @jgritty - 但你不能得到 39!如果你想称重 39 的东西,你需要 3 个元素:) 没关系,我有 4 个!但是,您显然最终会遇到问题。 @jgritty - 呃不,因为上面的方法解决了问题:1,3,9,27 :) @Abid Rahman K - 干杯,:) 我将通过一些代码和更多解释为这个答案提供一个非常有趣的更新。这是一个非常有趣的问题。使用这个方法可以让你轻松解决问题:`一个人放下一个 1000000 Kg 的重物,他的新重物可以让他称重到 1000000 Kg 的任何重量!它们是什么,有多少?【参考方案2】:

这是一个蛮力的 itertools 解决方案:

import itertools as it

def merchant_puzzle(weight, pieces):
    full = range(1, weight+1)
    all_nums = set(full)
    comb = [x for x in it.combinations(full, pieces) if sum(x)==weight]
    funcs = (lambda x: 0, lambda x: x, lambda x: -x)
    for c in comb:
        sums = set()
        for fmap in it.product(funcs, repeat=pieces):
            s = sum(f(x) for x, f in zip(c, fmap))
            if s > 0:
                sums.add(s)
                if sums == all_nums:
                    return c

>>> merchant_puzzle(40, 4)
(1, 3, 9, 27)

如需了解其工作原理,请查看the answer Avaris gave,这是相同算法的实现。

【讨论】:

哇!当我在写解释时,你在编写同样的代码:)。【参考方案3】:

你很接近,非常接近:)。

既然这是你想解决的难题,我就给点指点。对于这部分:

例如,如果 (a,b,c,d) 是梳子中的第一组值,我需要检查 a,b,c,d,a+b,ab, ........a+b+cd,a-b+c+d...... ..等等。

考虑一下:每个重量都可以放在一个秤上,另一个秤上,或者两者都没有。所以对于a 的情况,这可以表示为[a, -a, 0]。其他三个也一样。现在,您需要所有可能的配对以及每种重量的这 3 种可能性(提示:itertools.product)。然后,对配对的可能测量(假设:(a, -b, c, 0))仅仅是这些(a-b+c+0)的总和。

剩下的就是检查您是否可以“测量”所有需要的重量。 set 在这里可能会派上用场。

PS:正如 cmets 中所述,对于一般情况,这些划分的权重可能没有必要区分(对于这个问题)。你可以重新考虑itertools.combinations

【讨论】:

+1 - this can be represented as [a, -a, 0]。我没有想到这个技巧。那个单一的陈述就是转折点。谢谢。【参考方案4】:

第二部分我是蛮拼的。

如果您不想看到答案,请不要单击此按钮。显然,如果我更擅长排列,这将需要更少的剪切/粘贴搜索/替换:

http://pastebin.com/4y2bHCVr

【讨论】:

天哪,我不敢相信你写了这么多行!它适用于time = 0.185 s【参考方案5】:

我不知道 Python 语法,但也许你可以解码这个 Scala 代码;从第二个 for 循环开始:

def setTo40 (a: Int, b: Int, c: Int, d: Int) = 

val vec = for (
  fa <- List (0, 1, -1);
  fb <- List (0, 1, -1);
  fc <- List (0, 1, -1);
  fd <- List (0, 1, -1);
  prod = fa * a + fb * b + fc * c + fd * d;
  if (prod > 0)
  ) yield (prod)

  vec.toSet


for (a <- (1 to 9);
  b <- (a to 14);
  c <- (b to 20);
  d = 40-(a+b+c)
  if (d > 0))  
    if (setTo40 (a, b, c, d).size > 39)
      println (a + " " + b + " " + c + " " + d)
  

【讨论】:

【参考方案6】:

使用砝码 [2, 5, 15, 18],您还可以测量 1 到 40 公斤之间的所有物体,尽管其中一些需要间接测量。例如,要测量一个重 39 公斤的物体,您首先将其与 40 公斤进行比较,然后天平将悬垂到 40 公斤的一侧(因为 39 38),因此您可以得出物体重量为 39 公斤。

更有趣的是,使用 [2, 5, 15, 45] 砝码,您可以测量重量不超过 67 公斤的所有物体。

【讨论】:

您好,谢谢您的回答。我的问题不是要找到答案,而是以编程方式解决它,尤其是使用 python。 没关系,但请注意,编写程序来找到这个答案稍微复杂一些,因为您必须考虑间接测量。【参考方案7】:

如果有人不想导入库来导入连击/烫发,这将生成所有可能的 4 步策略...

# generates permutations of repeated values
def permutationsWithRepeats(n, v):
    perms = []
    value = [0] * n
    N = n - 1
    i = n - 1

    while i > -1:
        perms.append(list(value))

        if value[N] < v:
            value[N] += 1
        else:
            while (i > -1) and (value[i] == v):
                value[i] = 0
                i -= 1

            if i > -1:
                value[i] += 1
                i = N

    return perms

# generates the all possible permutations of 4 ternary moves
def strategy():
    move = ['-', '0', '+']
    perms = permutationsWithRepeats(4, 2)

    for i in range(len(perms)):
        s = ''

        for j in range(4):
            s += move[perms[i][j]]

        print s

# execute
strategy()

【讨论】:

【参考方案8】:

我的解决方法如下:

    #!/usr/bin/env python3

weight = 40
parts = 4
part=[0] * parts

def test_solution(p, weight,show_result=False):
    cv=[0,0,0,0]
    for check_weight in range(1,weight+1):
        sum_ok = False
        for parts_used in range(2 ** parts):
            for options in range(2 ** parts):
                for pos in range(parts):
                    pos_neg = int('0:01b'.format(options,parts)[pos]) * 2 - 1
                    use = int('0:01b'.format(parts_used,parts)[pos])
                    cv[pos] = p[pos] * pos_neg * use
                if sum(cv) == check_weight:
                    if show_result:
                        print(" = sum of:".format(check_weight, cv))
                    sum_ok = True
                    break
        if sum_ok:
            continue
        else:
            return False
    return True

for part[0] in range(1,weight-parts):
    for part[1] in range(part[0]+1, weight - part[0]):
        for part[2] in range( part[1] + 1 , weight - sum(part[0:2])):
            part[3] = weight - sum(part[0:3])
            if test_solution(part,weight):
                print(part)
                test_solution(part,weight,True)
                exit()

它为您提供给定权重的所有解决方案

【讨论】:

【参考方案9】:

比我之前的答案更有活力,因此它也适用于其他数字。但是分成 5 次和平需要一些时间:

#!/usr/bin/env python3

weight = 121
nr_of_parts = 5

# weight = 40
# nr_of_parts = 4

weight = 13
nr_of_parts = 3


part=[0] * nr_of_parts

def test_solution(p, weight,show_result=False):
    cv=[0] * nr_of_parts
    for check_weight in range(1,weight+1):
        sum_ok = False
        for nr_of_parts_used in range(2 ** nr_of_parts):
            for options in range(2 ** nr_of_parts):
                for pos in range(nr_of_parts):
                    pos_neg = int('0:01b'.format(options,nr_of_parts)[pos]) * 2 - 1
                    use = int('0:01b'.format(nr_of_parts_used,nr_of_parts)[pos])
                    cv[pos] = p[pos] * pos_neg * use
                if sum(cv) == check_weight:
                    if show_result:
                        print(" = sum of:".format(check_weight, cv))
                    sum_ok = True
                    break
        if sum_ok:
            continue
        else:
            return False
    return True

def set_parts(part,position, nr_of_parts, weight):
    if position == 0:
        part[position] = 1
        part, valid = set_parts(part,position+1,nr_of_parts,weight)
        return part, valid
    if position == nr_of_parts - 1:
        part[position] = weight - sum(part)
        if part[position -1] >= part[position]:
            return part, False
        return part, True
    part[position]=max(part[position-1]+1,part[position])
    part, valid = set_parts(part, position + 1, nr_of_parts, weight)
    if not valid:
        part[position]=max(part[position-1]+1,part[position]+1)
        part=part[0:position+1] + [0] * (nr_of_parts - position - 1)
        part, valid = set_parts(part, position + 1, nr_of_parts, weight)
    return part, valid

while True:
    part, valid = set_parts(part, 0, nr_of_parts, weight)
    if not valid:
        print(part)
        print ('No solution posible')
        exit()
    if test_solution(part,weight):
        print(part,'    ')
        test_solution(part,weight,True)
        exit()
    else:
        print(part,'   ', end='\r')

【讨论】:

以上是关于用 Python 解决难题的主要内容,如果未能解决你的问题,请参考以下文章

python 文本处理难题。 只能采用Python解决。要求最优效率最高解法,谢谢。

if/while/for 解决数学难题 Python实例

困扰70年的数学难题,华人带领MIT本科生用一个暑期项目就解决

用 C++ 解决数独难题

巧用Python脚本解决自动化图形验证码难题

区块链技术是怎么解决信任构建难题的?