Algorithm 换硬币时扭转(最小化重量数组)

Algorithm 换硬币时扭转(最小化重量数组),algorithm,coin-change,Algorithm,Coin Change,我正在开发一个应用程序,用于不到10种不同重量的训练。例如:锻炼可能需要{30,40,45,50,55,65,70,80}的重量 现在,用户不必确定要抓取多少45磅重、35磅重、25磅重等,应用程序也可以显示一个包含每个尺寸所需重量数量的表格,这将是一件好事 我的问题是,假设我们有无限数量的5磅、10磅、25磅、35磅和45磅权重,那么每个权重的最佳数量是多少,才能与数组中的每个权重相加?最理想的是总重量最少,其次是最轻的总重量 例如,假设我想优化{25,35,45},那么如果我的答案数组是{n

我正在开发一个应用程序,用于不到10种不同重量的训练。例如:锻炼可能需要{30,40,45,50,55,65,70,80}的重量

现在,用户不必确定要抓取多少45磅重、35磅重、25磅重等,应用程序也可以显示一个包含每个尺寸所需重量数量的表格,这将是一件好事

我的问题是,假设我们有无限数量的5磅、10磅、25磅、35磅和45磅权重,那么每个权重的最佳数量是多少,才能与数组中的每个权重相加?最理想的是总重量最少,其次是最轻的总重量

例如,假设我想优化{25,35,45},那么如果我的答案数组是{num_5lbs,num_10lbs,num_25lbs,num_35lbs,num_45lbs},我们可以做{0,0,1,1,1},但是总数是25+35+45=105lbs。我们也可以做{0,2,1,0,0},我认为这是最佳的,因为它有3个重量,但总重量只有45磅


另一个例子,假设我想优化{30,40,50},那么我们可以有{1,2,1,0,0}和{1,1,1,0}。两者都使用4个重量,但前者的总重量为5+20+25=50磅,而后者的总重量为5+10+25+35=75磅。

你在我身上抓住了竞争对手

使用动态编程(memoization),我能够将运行时降低到一个合理的水平

首先,我们定义我们拥有的权重类型,以及我们想要达到的目标

weights = [5, 10, 25, 35, 45]
targets = [30, 40, 45, 50, 55, 65, 70, 80]
接下来是主DP功能
walk
获取已使用权重的有序列表(最初为空)和一个
pos
告诉我们已经考虑了哪些权重(最初为零)。这将
walk
的调用次数从
O(n!)
减少到
O(2^n)
<代码>行走也会被记忆,从而将执行时间从
O(2^n)
进一步降低到
O(n)

有几个基本情况,其中一些是为提高性能而动态修改的:

  • pos>=len(权重)
    如果pos大于权重的长度,则我们已检查所有权重,并完成递归
  • len(已使用)>max(目标)/min(权重)
    这是要使用的权重数量的弱界限。如果有一种方法只使用最小的重量,但仍然通过最大的目标,我们知道我们已经检查了足够的数字,并且这个分支是无用的。继续
  • len(used)>bwnum
    其中
    bwnum
    是迄今为止最佳答案中使用的权重数。因为这是我们的主要标准,所以当我们选取的权重超过
    bwnum
    时,我们可以停止递归。这是一个很大的优化,假设我们很快找到任何有效的答案
对于
a
b
两种情况,我们可以在
pos
处选择另一种权重,也可以向前移动
pos
。最好的一个(最短的,然后是最小的总和)被记录并返回。因为有两种情况,我们有一个分支因子
2

mem = {}
bwnum = len(weights)+1

def walk(used, pos):
    k = (used, pos)
    global bwnum, weights, targets

    if pos >= len(weights) or len(used) > bwnum or len(used) > max(targets) / min(weights):
        return used if valid(used) else (1e9,)*(bwnum+1)

    if k not in mem:
        a = walk(used + (weights[pos],), pos)
        b = walk(used, pos + 1)

        mem[k] = a if len(a) < len(b) or (len(a) == len(b) and sum(a) < sum(b)) else b
        if valid(mem[k]):
            bwnum = min(bwnum, len(mem[k]))

    return mem[k]
最后,使用空参数调用
walk
,并打印结果

r = walk((), 0)
print r, len(r), sum(r)
哪些产出:

(5, 5, 10, 25, 35) 5 80

哦,顺便说一下,你的例子是对的。谢谢。

您可以将其作为整数线性规划问题来解决

引入整数变量
n5、n10、n25、n35、n45
,用于计算可能解决方案中每个权重的数量

优化目标是:

minimize (n5+n10+n25+n35+n45) * 1000 + 5*n5 + 10*n10 + 25*n25 + 35*n35 + n45
这里,
1000
是大于会话中出现的最大总权重的任何整数,此函数设计用于首先最大化权重总数,然后最大化总权重

接下来,假设
w[1]
w[k]
是您想要的目标权重。添加(非负)整数变量
a5[i]
a10[i]
a25[i]
a35[i]
a45[i]
(其中
i
范围超过
1
k
),并添加这些线性约束:

a5[i]*5 + a10[i]*10 + a25[i]*25 + a35[i]*35 + a45[i]*45 = w[i]
a5[i] <= n5
a10[i] <= n10
a25[i] <= n25
a35[i] <= n35
a45[i] <= n45
a5[i]*5+a10[i]*10+a25[i]*25+a35[i]*35+a45[i]*45=w[i]

a5[i]您的代码在我看来是正确的,但如果
walk()
的回忆录没有效果,因为如果权重不同,那么
walk()
将永远不会使用同一个参数对调用两次。(我相信;我很高兴被证明是错的!)此外,在没有进一步的论据解释为什么这不可能发生的情况下,
walk()
可以为使用的每个可能不同的
向量调用一次。假设所有权重和所有目标都是最小权重的倍数,并设r=舍入(minTarget/maxWeight);然后,对于每一个具有精确r(不一定不同)权重的多重权重集,都有一个不同的对应
使用的
向量,它是某个有效解决方案的一部分(因为我们可以向其中添加足够多的最小权重副本以达到每个目标)。。。。。。有(n+r-1选择r-1)这样的多集,这意味着可以用至少这么多不同的
使用的
向量来构造输入(这是n中的超指数)。我认为你对
行走的记忆是正确的:)它过去是有帮助的,但后来我改变了它,并将备忘录留在:)至于第二条评论:这就是我所说的优化。我没有实现它,因为它的回忆录“足够快”,我在凌晨3点左右写下了答案。你的意思是
walk
len(权重)
中是超指数的吗?或者
有效
?非常感谢您花时间写这篇文章!我将稍微研究一下,并尝试用Java重写它。
a5[i]*5 + a10[i]*10 + a25[i]*25 + a35[i]*35 + a45[i]*45 = w[i]
a5[i] <= n5
a10[i] <= n10
a25[i] <= n25
a35[i] <= n35
a45[i] <= n45