leetcode——322-CoinChange

题目:给你 k 种面值的硬币,面值分别为 c1, c2 ... ck,再给一个总金额 n,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,则回答 -1 。
比如说,k = 3,面值分别为 1,2,5,总金额 n = 11,那么最少需要 3 枚硬币,即 11 = 5 + 5 + 1 。

 

按照递归暴力解法->带备忘录的递归解法->非递归的动态规划解法的流程走一下子:

1.暴力递归解法:

首先也是最困难的找出状态转移矩阵,也就是递推公式:

 

这个方程就用到了「最优子结构」性质:原问题的解由子问题的最优解构成。即 f(11) 由 f(10), f(9), f(6) 的最优解转移而来。
要符合「最优子结构」,子问题间必须互相独立。

def coinChange(coins, amount):
    if amount==0:
        return 0
    res = -1
    for coin in coins:
        if amount-coin<0:
            continue
        subProb = coinChange(coins, amount - coin)
        if subProb == -1:
            continue
        if res == -1:
            res = subProb+1
        else:
            res = min(res,subProb+1)
    return res

2.带备忘录的递归解法:
 

int coinChange(vector<int>& coins, int amount) {
    // 备忘录初始化为 -2
    vector<int> memo(amount + 1, -2);
    return helper(coins, amount, memo);
}

int helper(vector<int>& coins, int amount, vector<int>& memo) {
    if (amount == 0) return 0;
    if (memo[amount] != -2) return memo[amount];
    int ans = INT_MAX;
    for (int coin : coins) {
        // 金额不可达
        if (amount - coin < 0) continue;
        int subProb = helper(coins, amount - coin, memo);
        // 子问题无解
        if (subProb == -1) continue;
        ans = min(ans, subProb + 1);
    }
    // 记录本轮答案
    memo[amount] = (ans == INT_MAX) ? -1 : ans;
    return memo[amount];
}

3.动态规划:

        其实这个题和跳台阶的思路是一样的,首先从结果(也就是最后一个状态),逆向往回考虑,只考虑一步,也就是之前的状态怎么样能一步到位,得出最终结果。这样就可以得到状态转移矩阵。但是不要用递归的方式,这回要用正向的考虑,从最简单的状态开始正向迭代,得到最终结果。

以凑零钱为例:比如最后要10元,那10之前的状态可以是5元,8元,9元;它们分别加上5元,2元,1元就得到了10元。以此类推就可以得到状态转移矩阵 dp[i] = 1+ min (dp[i-coins[0]], dp[i-coins[1]]......) ,得到了状态转移矩阵之后,我们要从最简单的状态开始:也就是0元,什么都没有。从0元开始凑钱。

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float("inf") for i in range(amount+1)] #初始化dp数组
        dp[0] = 0   # 当amount=0时,硬币所需数为0
        # 在同一个总额的情况下遍历所有的硬币金额
        for i in range(1, amount + 1):
            for coin in coins:
                if i - coin >= 0:
                    # 选取最小的硬币数
                    dp[i] = min(dp[i], dp[i - coin] + 1)
        if dp[-1] == float("inf"):
            return -1
        else:
            return dp[-1]

 

另一道题:

题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成[1, amount]中所有面值所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。(你可以认为每种硬币的数量是无限的。)

   dp[ i ] 代表凑成从1到 i 所有的金额所需的最少硬币个数。对于 dp[ s ]  = dp[ i ]+dp[ j ]时候,如果 j  在coins里,比如 j 是5,i 是
3,i 保证1,2,3都可以被表示,j 是一个单独的硬币,它只能保证 j 自己本身被表示,这样4就没有可被表示的方式了。所以就要使得i >=j-1,这要就保证s内所有的金额都能被表示。

s = i + j and i >= j-1  => j< = s - j + 1  (程序中用 i 表示 s )

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float("inf") for i in range(amount+1)] #初始化dp数组
        dp[0] = 0   # 当amount=0时,硬币所需数为0
        if 1 not in coins: # 如果硬币里面没有面值为1的硬币,则无法组成所有的硬币
            return -1 
        dp[1] = 1
        # 遍历总金额
        for i in range(2,amount+1):
            min_ = dp[i] 
            # 遍历之前已经凑成的金额
            for j in range(1,i):
                # 直接拿单个的硬币凑钱
                if j in coins and j<=i-j+1:  # 如果另一部分直接可以用一个硬币代替
                    min_ = min(min_, dp[i-j]+1)
                else:
                    min_ = min(min_,dp[i-j]+dp[j])
            dp[i]=min_
        return dp[-1]      

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值