改变硬币时扭转(最小化重量阵列)

时间:2016-04-28 21:26:41

标签: algorithm coin-change

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

现在用户不必确定要抓取多少45磅重量,35磅重量,25磅重量等等,并且应用程序显示每个尺寸的重量数量的表格将是一件好事。需要的。

我的问题是,鉴于我们有无限数量的5lb,10lb,25lb,35lb和45lb权重,每个能够与数组中每个权重相加的最佳数量是多少?最优的是总重量最少,然后是最轻的总重量。

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

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

2 个答案:

答案 0 :(得分:1)

你抓住了我的竞争程序员。

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

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

weights = [5, 10, 25, 35, 45]
targets = [30, 40, 45, 50, 55, 65, 70, 80]

接下来我们有主要的DP功能。 walk获取已使用权重的有序列表(最初为空)和pos,告诉我们已考虑的权重(最初为零)。这会将walk的{​​{1}}来电次数从O(n!)降低到O(2^n)walk也会被记忆,从而将执行时间从O(2^n)降低到O(n)

有一些基本情况,其中一些是针对性能进行动态修改的:

  • pos >= len(weights)如果pos大于权重的长度,我们检查了所有权重,并且我们完成了递归。
  • len(used) > max(targets) / min(weights)这是要使用的权重数量的弱限制。如果有一种方法可以使用最小的重量并仍然通过最大的目标,我们知道我们已经检查了足够的数量,并且这个分支是无用的。继续前进。
  • len(used) > bwnum其中bwnum是目前为止最佳答案中使用的权重数。由于这是我们的主要标准,因此当我们选择超过bwnum个权重时,我们可以停止递归。假设我们很快找到任何有效的答案,这是一个很大的优化。

对于ab这两个案例,我们可以在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]

然后我们需要一个验证器功能,以检查给定的权重列表是否足以达到所有目标。这可以进一步优化,但速度非常快。我将80%的执行时间花在了这个功能上。

from itertools import combinations

vmem = {}


def valid(used):
    if used not in vmem:
        tmap = {}
        for t in targets:
            tmap[t] = 0

        for le in range(1, len(used) + 1):
            for c in combinations(used, le):
                if sum(c) in tmap:
                    del tmap[sum(c)]

        vmem[used] = len(tmap) == 0

    return vmem[used]

最后,使用空参数调用walk,然后打印结果。

r = walk((), 0)
print r, len(r), sum(r)

哪个输出:

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

哦,顺便说一下,你的例子是正确的。感谢。

答案 1 :(得分:1)

您可以将其解决为整数线性编程问题。

引入整数变量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转到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

我不知道在您的应用程序中包含ILP解算器而不是强力解决方案,甚至只是使用启发式方法来生成局部最优但不一定是全局最优的解决方案是有意义的。