修复硬币找零算法中的记忆化实现问题
修复你的递归+记忆化硬币找零方案
老哥,我明白你想通过优化自己的递归解法来理解原理,而不是直接换其他思路——这种抠细节的学习方式真的很棒!
问题核心:记忆化的键缺了关键状态
你现在的记忆化字典只把目标金额n当键,但实际上,不同的可用硬币范围下,同一个n的找零方式数是完全不同的。举个例子:
- 当n=10,只能用5的时候,只有1种找零方式(5+5);
- 当n=10,可以用5和10的时候,有2种找零方式(5+5、10)。
你的代码里第一次计算n=10时是在「只能用5」的场景,把d[10]设为1,后面再遇到「可以用5和10」的n=10时,直接返回了这个错误的结果,自然就漏掉了(10,5,5)这种组合。
解决方案:把「可用硬币范围」加入记忆化键
我们需要把记忆化的键从单纯的n,改成(n, 当前可用的硬币特征)——这里的特征可以是允许使用的硬币元组,或者更高效的、排序后硬币的最大索引。
方案1:贴合你原逻辑的修改
直接把当前允许使用的硬币列表转成元组(因为列表不能当字典键),作为记忆化键的一部分:
d = {} def make_change(n, coins): # 先排序硬币,避免因原顺序不同导致的重复场景 sorted_coins = sorted(coins) # 用(n, 硬币元组)作为记忆化键 key = (n, tuple(sorted_coins)) if key in d: return d[key] # base case if n < 0: return 0 if n == 0: return 1 total = 0 for coin in sorted_coins: # 只允许使用<=当前硬币的币种,和你原逻辑一致 allowed_coins = [c for c in sorted_coins if c <= coin] total += make_change(n - coin, allowed_coins) d[key] = total return total # 测试:应该返回3 print(make_change(20, [5,10]))
方案2:更高效的索引优化
我们可以先把硬币排序,然后用「最大硬币的索引」来代替整个硬币列表,这样键的体积更小,查找和存储效率更高:
d = {} def make_change(n, coins, max_idx): # 用(n, 最大硬币索引)作为记忆化键 key = (n, max_idx) if key in d: return d[key] # base case if n < 0: return 0 if n == 0: return 1 total = 0 # 遍历从0到max_idx的所有硬币 for i in range(max_idx + 1): current_coin = coins[i] # 递归时限制只能用当前硬币及更小的(即索引<=i) total += make_change(n - current_coin, coins, i) d[key] = total return total # 调用前先排序硬币,传入最后一个硬币的索引 sorted_coins = sorted([5,10]) print(make_change(20, sorted_coins, len(sorted_coins)-1)) # 返回3
为什么这样能解决问题?
现在记忆化的键包含了当前递归的完整状态:不仅有目标金额n,还有当前允许使用的硬币范围。这样不同场景下的结果会被分别存储,不会互相覆盖,自然就能统计到所有唯一的找零方式了。
内容的提问来源于stack exchange,提问作者Optimesh
相关产品推荐
相关产品推荐

