leetcode:
题目
思路
(1)装满容量为j的背包,使用的最少硬币数为dp[j]
(2)如果包含coins[i],凑足总额为j - coins[i]的最少个数为dp[j - coins[i]],那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j]。
所以dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的。
递推公式:dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
(3)凑足总金额为0所需钱币的个数一定是0,那么dp[0] = 0;
考虑到递推公式的特性,dp[j]必须初始化为一个最大的数,否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中被初始值覆盖。
所以下标非0的元素都是应该是最大值。
(4)本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数。
所以本题并不强调集合是组合还是排列。
本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序
遍历过程中需要加一个判断条件:
if (dp[j - coins[i]] != INT_MAX) { // 如果dp[j - coins[i]]是初始值则跳过
dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
防止dp[j - coins[i]] + 1超出内存限制!
代码如下:
#include <iostream>
#include <vector>
using namespace std;
class Solution
{
public:
/**
* 解决零钱兑换问题,计算组成总金额所需的最少硬币数量
* @param coins 可用的硬币面额数组
* @param amount 目标总金额
* @return 组成总金额所需的最少硬币数量,如果无法组成则返回-1
*/
int coinChange(vector<int> &coins, int amount)
{
// 初始化动态规划数组,大小为amount+1,初始值设为INT_MAX表示不可达
vector<int> dp(amount + 1, INT_MAX);
// 金额为0时,需要0个硬币
dp[0] = 0;
// 遍历每个硬币面额
for (int i = 0; i < coins.size(); i++)
{
// 从当前硬币面额开始,遍历到目标总金额
for (int j = coins[i]; j <= amount; j++)
{
// 如果当前金额减去当前硬币面额后的金额可达(if的作用是防止溢出)
if (dp[j - coins[i]] != INT_MAX)
{
// 更新dp[j]为当前金额可达的最小硬币数量
dp[j] = min(dp[j], dp[j - coins[i]] + 1);
}
}
}
// 如果目标总金额不可达,返回-1
if (dp[amount] == INT_MAX)
return -1;
// 返回组成总金额所需的最少硬币数量
return dp[amount];
}
};
总结
我第一遍写的时候
if (dp[j - coins[i]] != INT_MAX)
这个判断语句没有加,数据会溢出报错。
还有个好方法,就是在初始化dp的时候使用INT_MAX-1。