【问题标题】:Coin change with limited coins complexity硬币复杂性有限的硬币变化
【发布时间】:2017-10-28 00:44:02
【问题描述】:

如果每个硬币的数量不受限制,那么复杂度为O(n*m),其中n 是总零钱,m 是硬币类型的数量。现在,当每种类型的硬币都受到限制时,我们必须考虑剩余的硬币。我设法使它与O(n*m<sup>2</sup>) 的复杂性一起使用另一个大小为n 的for,这样我就可以跟踪每种类型的剩余硬币。有没有办法让复杂性变得更好?编辑:问题是计算进行确切给定更改所需的最少硬币数量以及我们使用每种硬币类型的次数

【问题讨论】:

  • 为什么需要另一个 for 循环?
  • 对于有限的情况,我们必须使用数组 dp[total_change][total_supply] 并相应地更改其值,以便我们在其迭代中具有不同的条件。至少我是这样使用它的。我的条件是 if dp[j - coin_type[i]][i] >0 where j>0, j0, i
  • 如有任何疑问,请随时咨询。
  • 所以使用我的方法,时间复杂度将保持 O(n*m)。
  • 问题在于,在动态编程实现中,您正在间接计算结果,所以我不可能知道何时减少当前数量。 @SumeetSingh

标签: algorithm dynamic-programming


【解决方案1】:

不需要额外的循环。您需要:

  • 递归深度最多为 m(硬币数量)级别,每个递归级别处理一个特定的硬币。
  • 在每个递归级别上最多循环 n 次,以确定您将从给定硬币中取出多少。

下面是代码在 Python 3 中的样子:

def getChange(coins, amount, coinIndex = 0):
    if amount == 0:
        return [] # success
    if coinIndex >= len(coins):
        return None # failure
    coin = coins[coinIndex]
    coinIndex += 1
    # Start by taking as many as possible from this coin
    canTake = min(amount // coin["value"], coin["count"])
    # Reduce the number taken from this coin until success
    for count in range(canTake, -1, -1): # count will go down to zero
        # Recurse to decide how many to take from the next coins
        change = getChange(coins, amount - coin["value"] * count, coinIndex)
        if change != None: # We had success
            if count: # Register this number for this coin:
                return change + [{ "value": coin["value"], "count": count }]
            return change


# Example data and call:
coins = [
    { "value": 20, "count":  2 },   
    { "value": 10, "count":  2 },
    { "value":  5, "count":  3 },
    { "value":  2, "count":  2 },
    { "value":  1, "count": 10 }
]

result = getChange(coins, 84)
print(result)

给定示例的输出:

[
    {'value': 1, 'count': 5},
    {'value': 2, 'count': 2},
    {'value': 5, 'count': 3},
    {'value': 10, 'count': 2},
    {'value': 20, 'count': 2}
]

尽量减少使用的硬币数量

如 cmets 中所述,上述算法返回它找到的第一个解。如果要求在有多个解决方案时必须最小化单个硬币的数量,那么您不能在循环中途return,而必须保留迄今为止找到的“最佳”解决方案。

这里是修改后的代码来实现这一点:

def getchange(coins, amount):
    minCount = None

    def recurse(amount, coinIndex, coinCount):
        nonlocal minCount
        if amount == 0:
            if minCount == None or coinCount < minCount:
                minCount = coinCount
                return [] # success
            return None # not optimal
        if coinIndex >= len(coins):
            return None # failure
        bestChange = None
        coin = coins[coinIndex]
        # Start by taking as many as possible from this coin
        cantake = min(amount // coin["value"], coin["count"])
        # Reduce the number taken from this coin until 0
        for count in range(cantake, -1, -1):
            # Recurse, taking out this coin as a possible choice
            change = recurse(amount - coin["value"] * count, coinIndex + 1, 
                                                             coinCount + count)
            # Do we have a solution that is better than the best so far?
            if change != None: 
                if count: # Does it involve this coin?
                    change.append({ "value": coin["value"], "count": count })
                bestChange = change # register this as the best so far
        return bestChange

    return recurse(amount, 0, 0)

coins = [{ "value": 10, "count":  2 },
         { "value":  8, "count":  2 },
         { "value":  3, "count": 10 }]

result = getchange(coins, 26)
print(result)

输出:

[
    {'value': 8, 'count': 2},
    {'value': 10, 'count': 1}
]

【讨论】:

  • 硬币值为 10、8 和 3,目标为 26,这将返回 2 个 3 和 2 个 10,而不是更优的 1 10 和 2 个 8。
  • @Dukeling,真的。在 OP 的问题中没有提到什么被认为是最佳的,但为了以防万一,我添加了一个代码变体,它将返回一个最小化使用的单个硬币数量的解决方案。
  • 在最坏的情况下,这两种方法看起来都需要指数级的时间,这比动态编程方法要慢得多。 (循环n次迭代的m的递归深度是O(n^m),而不是O(mn))
【解决方案2】:

这是一个用 Python 实现的 O(nm) 解决方案。

如果定义了C(c, k) = 1 + x^c + x^(2c) + ... + x^(kc),则程序计算多项式product(C(c[i], k[i]), i = 1...ncoins) 的第一个n+1 系数。该多项式的j'th 系数是对j 进行更改的方式数。

当所有ks 都是无限的时,这个多项式乘积很容易计算(参见,例如:https://stackoverflow.com/a/20743780/1400793)。在有限的情况下,需要能够有效地计算k 项的运行总和,这是在程序中使用rs 数组完成的。

# cs is a list of pairs (c, k) where there's k
# coins of value c.
def limited_coins(cs, n):
    r = [1] + [0] * n
    for c, k in cs:
        # rs[i] will contain the sum r[i] + r[i-c] + r[i-2c] + ...
        rs = r[:]
        for i in xrange(c, n+1):
            rs[i] += rs[i-c]
            # This line effectively performs:
            # r'[i] = sum(r[i-j*c] for j=0...k)
            # but using rs[] so that the computation is O(1)
            # and in place.
            r[i] += rs[i-c] - (0 if i<c*(k+1) else rs[i-c*(k+1)])
    return r[n]

for n in xrange(50):
    print n, limited_coins([(1, 3), (2, 2), (5, 3), (10, 2)], n)

【讨论】:

  • 如 cmets 中所述,问题是计算所需的最少硬币数量,而不是到达那里的总路线数。
猜你喜欢
  • 2011-05-11
  • 2016-01-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-04
  • 2014-01-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多