(leetcode322)零钱兑换 (贪心+dfs、动态规划之自顶向下和自底向上)

leetcode原题,如图:
在这里插入图片描述

1、贪心+dfs

笔者第一想法是,得到所有数值和为amount的组合,取其中最短的组合长度返回,没有则返回-1。
根据这个思路,笔者做了深度优先遍历dfs。
初始因为剪枝不足而总是超时,最终代码为:

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount==0:
            return 0
        coins.sort()
        global ret
        ret=float("inf")
        def dfs(n,target,big):
            #print(n,target,big)
            global ret
            #剪枝1,target比最小的面值还小,直接返回
            if target<coins[0]:
                return
            #得到一种组合,更新ret
            if target in coins:
                ret=min(ret,n+1)
                return
            for index in range(big,-1,-1):
            	#剪枝2,最大次数*最大面值<target,直接返回
                if (ret-n)*coins[index]<target:
                    break
                #剪枝3,本次面值大于target,因为coins排序后单调递减,进入下一面值即可
                if coins[index]>target:
                    continue
                else:
                	#直接dfs
                    dfs(n+1,target-coins[index],index)
        dfs(0,amount,len(coins)-1)
        if ret!=float("inf"):
            return ret
        else:
            return -1

其中,最重要的剪枝在这里:

				if (ret-n)*coins[index]<target:
                    break

即当当前最大可能的次数(ret-n),乘以最大的面值(coins[index],因为是从大到小的)也不能满足条件直接break。
加这句之前超时,加上之后超过97.6%的提交,没错,就是这么变态。

另外,在查看题解的时候学到了python中表示最大数值和最小数值的方式:

max_int=float("inf")
min_int=float("-inf")

2、动态规划

这是官方题解中给出的两种动态规划的方法:
自顶向下和自底向上。
原文链接
状态转移方程是:dp[n]=dp[n-coin]+1

(1)自顶向下

递归过程,从最大的amount开始,逐渐减coin。
递归出口:
(1)小于0表示上层递归时,硬币面值大于目标,返回-1;
(2)等于0表示上层递归时,硬币面值等于目标,返回0,上层递归时可进入if,最终传回1;
(3)如果for中减去所有硬币都没有修改mini值,就返回-1,表示本层也没有硬币组合可以组成目标。
这里非常非常非常有用的一点

import functools
@functools.lru_cache(amount)

笔者理解是一种python装饰器,lru_cache的功能是暂存最大amount大小的结果,当再次调用dp函数时直接返回而不会再次进入函数,从而大大减少重复计算。

import functools
def coinChange(coins,amount):
        @functools.lru_cache(amount)
        def dp(rem):
            if rem < 0: return -1
            if rem == 0: return 0
            mini = int(1e9)
            for coin in coins:
                res = dp(rem - coin)
                if res >= 0 and res < mini:
                    mini = res + 1
            return mini if mini < int(1e9) else -1

        if amount < 1: return 0
        return dp(amount)

(2)自底向上

非常清楚。

def coinChange(coins,amount):
    dp=[float("inf")]*(amount+1)
    dp[0]=0
    for coin in coins:
        for index in range(coin,amount+1):
            dp[index]=min(dp[index],dp[index-coin]+1)

    return dp[amount] if dp[amount]<float("inf") else -1

猜你喜欢

转载自blog.csdn.net/qq_41584385/article/details/106006423