Leetcode Link: 剑指 Offer II 103. 最少的硬币数目 - 力扣(LeetCode)

题目

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

从暴力递归到动态规划

暴力递归

典型的从左到右的尝试模型,思路和代码都和零钱兑换几个问题比较相似

然而本题的问题变成了最小的隐硬币数量

所以我们需要知道如何”返回最小硬币的数量”,并且需要知道如何处理异常情况

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        res = self.process(coins, 0, amount)
        return -1 if res==float('inf') else res
    
    def process(self, coins, idx, rest):
	    ### 尽量使用 == 来确定base case
	    ### 实际上我们能够进一步简化,譬如 rest == 0时就可以直接返回了
        if idx == len(coins) and rest == 0:
            return 0
        if idx == len(coins) and rest > 0:
            return -1
        # rest < 0 的我就不让进来了,所以不写这个base case
 
        res = float('inf')
        n = 0
        while(rest-n*coins[idx]>=0):
            tmp = self.process(coins, idx+1, rest-n*coins[idx])
            if tmp != -1:  # 异常情况不能和结果比较
                res = min(res, tmp+n)  # 记得加上当前使用的货币数量
            n += 1
 
        return res

动态规划

比较简单,直接改就行, 但是依然超时,主要原因是while那里遇到amount很大但是面值很小的时候,会浪费大量时间

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # [idx, rest]
        dp = [[0 for _ in range(amount+1)] for _ in range(len(coins)+1)]
        # init
        dp[len(coins)][0] = 0
        for i in range(1, amount+1):
            dp[len(coins)][i] = -1
        
        for idx in range(len(coins)-1, -1, -1):
            for rest in range(amount+1):
                dp[idx][rest] = float('inf')
                n = 0
                while(rest-n*coins[idx]>=0):
                    tmp = dp[idx+1][rest-n*coins[idx]]
                    if tmp != -1:  # 异常情况不能和结果比较
                        dp[idx][rest] = min(dp[idx][rest], tmp+n)  # 记得加上当前使用的货币数量
                    n += 1
        return -1 if dp[0][amount]== float('inf') else dp[0][amount]

动态规划(状态压缩)

根据前面的代码画出空间位置依赖图

√依赖 (a+0, b+1, c+2, …) x 依赖 (b+0, c+1, d+3)…

所以,√ = min (x+1, a)

但是,我们需要处理异常值情况。 在上面的代码中,我们在base case中设置了异常为-1,而在一般case中,如果所有情况都为异常,则会返回的是 float('inf'),即在一般case中我们的异常为 float('inf')

在这个代码中,我们会直接比较左和下的格子,这样一定会把-1的异常值一直带着,所以这里是有问题的,我们需要将异常值全部改为flaot('inf'),并且修改部分代码逻辑

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # [idx, rest]
        dp = [[0 for _ in range(amount+1)] for _ in range(len(coins)+1)]
        # init
        dp[len(coins)][0] = 0
        for i in range(1, amount+1):
            dp[len(coins)][i] = float('inf') # 异常情况
        
        for idx in range(len(coins)-1, -1, -1):
            for rest in range(amount+1):
                if rest-coins[idx] >= 0:
                    dp[idx][rest] = min(dp[idx+1][rest], dp[idx][rest-coins[idx]]+1)
                else:
                    dp[idx][rest] = dp[idx+1][rest]
        return -1 if dp[0][amount]== float('inf') else dp[0][amount]
 
 

启发和联系