简体   繁体   English

动态规划:在 ProjectEuler 上的问题 31 上将自下而上的方法转换为自上而下的方法

[英]Dynamic Programming: Convert bottom-up approach to top-down on Problem 31 on ProjectEuler

For the sake of learning, I want to solve [problem 31] ( https://projecteuler.net/problem=31 ) on Project Euler using top-down DP.为了学习,我想使用自上而下的 DP 解决 Project Euler 上的 [问题 31] ( https://projecteuler.net/problem=31 )。 I have already solved it using both brute force and bottom-up Dynamic Programming.我已经使用蛮力和自下而上的动态编程解决了它。

Problem summary: Number of unique combinations (order doesn't matter) in which sums to £2, having the coins 1p, 2p, 5p, 10p, 20p, 50p, £1, £2.问题总结:总和为 2 英镑的独特组合的数量(顺序无关紧要),硬币为 1p、2p、5p、10p、20p、50p、1 英镑、2 英镑。

I have done several attempts, but my result does take order into account.我做了几次尝试,但我的结果确实考虑到了顺序。 I tried to exclude this by including an "upperLimit" variable, whose job is to never include a more valuable coin (eliminating possibilities like 5,1,2,1, instead of 5,2,1,1)我试图通过包含一个“upperLimit”变量来排除这一点,它的工作是永远不会包含一个更有价值的硬币(消除像 5,1,2,1 这样的可能性,而不是 5,2,1,1)

However, it still doesn't yield correct results.但是,它仍然没有产生正确的结果。 This is my code, and below the TOP DOWN I've included the bottom-up approach which works.这是我的代码,在 TOP DOWN 下面我包含了有效的自下而上方法。

Lets simplify for 5p instead of 200p, with the coins 1,2,5.让我们简化为 5 便士而不是 200 便士,硬币为 1、2、5。 This should yield 4 combinations: 5, 221, 2111, 11111.这应该产生 4 种组合:5、221、2111、11111。

My code with top-down outputs 5 combinations, while the bottom up correctly outputs 4.我的自上而下输出 5 种组合的代码,而自下而上正确输出 4 种组合。

TOP-DOWN APPROACH (doesn't work yet)自上而下的方法(还没有用)

combos = [0 for x in range(0,201)]


def combinations(currentValue, upperLimit = 200):
    #Reset counter
    numbOfCombos = 0

    #If we reach leaf
    if currentValue == 0:
        return 1    

    #If the value is already known, return it
    elif combos[currentValue] != 0:
        return combos[currentValue]

    #Else recurse through the tree
    else:
        if currentValue >= 200:
            numbOfCombos = numbOfCombos + combinations(currentValue-200, 200)
        if currentValue >= 100 and upperLimit >= 100:
            numbOfCombos = numbOfCombos + combinations(currentValue-100, 100)
        if currentValue >= 50 and upperLimit >= 50:
            numbOfCombos = numbOfCombos + combinations(currentValue-50, 50)
        if currentValue >= 20 and upperLimit >= 20:
            numbOfCombos = numbOfCombos + combinations(currentValue-20, 20)
        if currentValue >= 10 and upperLimit >= 10:
            numbOfCombos = numbOfCombos + combinations(currentValue-10, 10)
        if currentValue >= 5 and upperLimit >= 5:
            numbOfCombos = numbOfCombos + combinations(currentValue-5, 5)
        if currentValue >= 2 and upperLimit >= 2:
            numbOfCombos = numbOfCombos + combinations(currentValue-2, 2)
        if currentValue >= 1 and upperLimit >= 1:
            numbOfCombos = numbOfCombos + combinations(currentValue-1, 1)

        combos[currentValue] = numbOfCombos
        return combos[currentValue]

print(combinations(5,))

BOTTOM-UP APPROACH (works)自下而上的方法(有效)

targetValue = 200;

coins = [1, 2, 5, 10, 20, 50, 100, 200];
combinations = [0 for x in range(0,targetValue+1)];
combinations[0] = 1;

for i in range(0, len(coins)):
    for j in range(coins[i], targetValue+1):
        combinations[j] = combinations[j] + combinations[j - coins[i]];

print(combinations);

Output

Any tips/advice or complete solutions are greatly appreciated.非常感谢任何提示/建议或完整的解决方案。 I know that the bottom-up solution is probably the most efficient and most beautiful, but for the sake of learning thought processes I'd like to solve it using TOP-DOWN.我知道自下而上的解决方案可能是最有效和最漂亮的,但为了学习思维过程,我想使用自上而下解决它。

Thanks!谢谢!

It's counting 1,1,2,1 because when it's counting solutions with 2,*, it memoizes 2 solutions for 3p, then counts 2 solutions for 1,1,*.它正在计算 1,1,2,1,因为当它计算 2,* 的解决方案时,它会为 3p 记住 2 个解决方案,然后为 1,1,* 计算 2 个解决方案。

The fix is to include upperLimit in the memoization.解决方法是在记忆中包含upperLimit (I also added a missing test upperLimit >= 200 .) See below. (我还添加了一个缺失的测试upperLimit >= 200 。)见下文。

combos = {}


def combinations(currentValue, upperLimit=200):
    # Reset counter
    numbOfCombos = 0

    # If we reach leaf
    if currentValue == 0:
        return 1

    # If the value is already known, return it
    elif (currentValue, upperLimit) in combos:
        return combos[currentValue, upperLimit]

    # Else recurse through the tree
    else:
        if currentValue >= 200 and upperLimit >= 200:
            numbOfCombos = numbOfCombos + combinations(currentValue - 200, 200)
        if currentValue >= 100 and upperLimit >= 100:
            numbOfCombos = numbOfCombos + combinations(currentValue - 100, 100)
        if currentValue >= 50 and upperLimit >= 50:
            numbOfCombos = numbOfCombos + combinations(currentValue - 50, 50)
        if currentValue >= 20 and upperLimit >= 20:
            numbOfCombos = numbOfCombos + combinations(currentValue - 20, 20)
        if currentValue >= 10 and upperLimit >= 10:
            numbOfCombos = numbOfCombos + combinations(currentValue - 10, 10)
        if currentValue >= 5 and upperLimit >= 5:
            numbOfCombos = numbOfCombos + combinations(currentValue - 5, 5)
        if currentValue >= 2 and upperLimit >= 2:
            numbOfCombos = numbOfCombos + combinations(currentValue - 2, 2)
        if currentValue >= 1 and upperLimit >= 1:
            numbOfCombos = numbOfCombos + combinations(currentValue - 1, 1)

        combos[currentValue, upperLimit] = numbOfCombos
        return numbOfCombos


print(combinations(5))

Posted top down approach can be simplified (ie less conditionals) and generalized to any set of coins using Coin Change |使用Coin Change | Posted top down 方法可以简化(即更少的条件)并推广到任何一组硬币。 DP-7 DP-7

For a set of coin S, we split the solution space into two sets:对于一组硬币 S,我们将解空间分成两组:

  1. Solutions that do not contain mth coin (or S[m).不包含第 m 个硬币(或 S[m)的解决方案。
  2. Solutions that contain at least one S[m].包含至少一个 S[m] 的解。 Let count(S, m, target) be the function to count the number of solutions, then it can be written as sum of count(S, m-1, target) and count(S, m, target-S[m]).令 count(S, m, target) 为 function 来计算解的个数,那么它可以写成 count(S, m-1, target) 和 count(S, m, target-S[m] ).

Code代码

def count(S, target):
    '''
        Returns the count of ways we can sum
        S[0...m-1] coins to get sum target
    
    '''
    def recur(m, target):
        # Helper function to enable Memoization 
        # If target is 0 then there is 1
        # solution (do not include any coin)
        if (target == 0):
            return 1

        # target < 0 -> no solution exists
        if (target < 0):
            return 0
   
        # no coins and target > 0 -> no solution
        if (m <=0 and target > 0):
            return 0
         
        # Check if coin combination and target amount have been solved before
        if (m, target) in lookup:
            return lookup[(m, target)]

        # sum of two solutions:
        # 1.  does not use m coin i.e.            recur (m-1, target)
        # 2.  uses at least one of m-th coin i.e. recur(m, target - S[m-1])
        # including S[m-1] (ii) excluding S[m-1]
        lookup[(m, target)] = recur(m - 1, target ) + recur(m, target - S[m-1])
        
        return lookup[(m, target)]
    
    lookup = {}
    
    return recur(len(S), target)

Test测试

arr = [1, 2, 5, 10, 20, 50, 100, 200]
print(count(arr, 200))
# Output: 73682

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM