动态规划LeetCode-322.零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

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

示例 1:

输入:coins =[1, 2, 5], amount =11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

输入:coins =[2], amount =3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:

完全背包问题的定义

完全背包问题是一种特殊的背包问题,其特点是:

  • 每种物品可以无限次使用(即每种硬币可以无限次使用)。

  • 需要选择若干件物品(硬币),使得这些物品的总价值(总金额)达到某个目标值(目标金额),并且总价值尽可能接近目标值。

零钱兑换问题与完全背包问题的对应关系

在 LeetCode 322. 零钱兑换问题中:

  • 物品:硬币,每种硬币可以无限次使用。

  • 价值:硬币的面值。

  • 目标值:目标金额 amount

  • 目标:找到最少的硬币数量,使得这些硬币的总金额等于目标金额。

这与完全背包问题的定义完全一致。

 dp[j]=min(dp[j],dp[j−coins[i]]+1)
 

动规五部曲(dp含义、递推公式、初始化、遍历顺序、打印数组)

题目及思路:动态规划呢主要的就是把大问题拆解成小问题,小问题的值保存下来,从小问题的答案推导到最终答案,空间换时间。
那这题求的是可以凑成总金额所需的 最少的硬币个数 。那我们可以把凑足总额为j所需钱币的最少个数都记录下来

所以我们dp含义为:dp[j]表示凑足金额j所需钱币的最少个数。

递推公式:对于每个金额 j,如果可以通过硬币 coins[i] 来凑出金额 j,那么状态转移方程应该是:

dp[j]=min(dp[j],dp[j−coins[i]]+1)
这里 dp[j - coins[i]] + 1 表示在金额 j - coins[i] 的基础上加上当前硬币 coins[i],从而凑出金额 j。注意最终dp值保存的是所需硬币的最少个数,是个数。

初始化:我们知道凑足金额j为0所需要钱币的最少个数为0,所以dp[0]=0。然后其他下标值,考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。所以:



遍历顺序:完全背包问题

如果求组合数就是外层for循环遍历物品,内层for遍历背包

如果求排列数就是外层for遍历背包,内层for循环遍历物品
本题求钱币的最小个数,那么钱币有顺序和没有顺序都可以,不影响钱币的最小个数。
这题并不强调是组合还是排列,所以都可以。我的代码以求组合数来求。


我们这里使用递推公式得出dp[j]有个if条件,if(dp[j-coins[i]] != INT_MAX),

如果dp[j - coins[i]]不等于初始值INT_MAX的话,才进行更新。那为什么要有这个条件呢?比如,假设当前j是5,coins[i]是3,那么j - coins[i]是2。如果dp[2]还是INT_MAX的话,说明用这个硬币的话,无法得到金额2,那么这时候即使加上一个3元的硬币,也无法得到5元,所以这个时候就不能更新dp[5],因为原来的dp[5]可能已经被其他硬币组合更新过,或者还没有被更新过,但此时这个路径是无效的。

举个例子,比如coins是[2],amount是3。这时候,dp数组初始化为INT_MAX。然后遍历硬币2的时候,内层循环从2开始。当j=2时,dp[2 -2]=dp[0]=0,所以dp[2]=0+1=1。当j=3时,j-2=1,此时dp[1]还是INT_MAX,所以不会执行min操作。这时候,dp[3]仍然保持INT_MAX,最终返回-1。这样正确的结果就得到了。

打印数组:当遇到疑惑或者提交错误时,打印数组出来比较快速的看看哪一步有错。

以下是我在力扣c语言提交的代码,仅供参考:
 

#include <stdio.h>  
#include <limits.h>  
#include <string.h>  
int coinChange(int* coins, int coinsSize, int amount) {
   int dp[amount+1];

   //初始化
    for (int i = 0; i <= amount; i++) {
        dp[i] = INT_MAX;
    }
   dp[0] = 0;

   for(int i = 0;i<coinsSize;i++)
   {
    for(int j = coins[i];j<=amount;j++)
    {
        if(dp[j-coins[i]] != INT_MAX)
        {
            dp[j] = dp[j-coins[i]]+1 < dp[j] ? dp[j-coins[i]]+1 : dp[j];
        }
    }
   }

   if (dp[amount] == INT_MAX) return -1;
    return dp[amount];
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值