leetcode-master/problems/0322.零钱兑换.md

5.0 KiB
Raw Blame History

思路

  • 确定dp数组以及下标的含义 dp[j]凑足总额为j所需钱币的最少个数为dp[j]

  • 确定递推公式

得到dp[j]考虑coins[i]只有一个来源dp[j - coins[i]]没有考虑coins[i])。

凑足总额为j - coins[i]的最少个数为dp[j - coins[i]]那么只需要加上一个钱币coins[i]即dp[j - coins[i]] + 1就是dp[j]考虑coins[i]

所以dp[j] 要去所有 dp[j - coins[i]] + 1 中最小的。

递推公式dp[j] = min(dp[j - coins[i]] + 1, dp[j]);

  • dp数组如何初始化

首先凑足总金额为0所需钱币的个数一定是0那么dp[0] = 0;

其他下标对应的数值呢?

考虑到递推公式的特性dp[j]必须初始化为一个最大的数否则就会在min(dp[j - coins[i]] + 1, dp[j])比较的过程中比初始值覆盖。

所以下标非0的元素都是应该是最大值。

代码如下:

vector<int> dp(amount + 1, INT_MAX);
dp[0] = 0;
  • 确定遍历顺序

求钱币最小个数,那么钱币有顺序,和钱币没有顺序都可以,都不影响钱币的最小个数。可以用背包组合方式或者排列方式来求。

如果本题要是求组成amount的有几种方式那么钱币循序就有影响了。

所以两个for循环的关系是coins放在外循环target在内循环、或者target放在外循环coins在内循环都是可以的

那么我采用coins放在外循环target在内循环的方式。

本题钱币数量可以无限使用,那么是完全背包。所以遍历的内循环是正序

综上所述遍历顺序为coins放在外循环target在内循环。且内循环正序。

C++ 代码如下:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 0 ;i < coins.size(); i++) { // 遍历钱币
            for (int j = coins[i]; j <= amount; j++) { // 遍历target
                if (dp[j - coins[i]] != INT_MAX) { // 如果dp[j - coins[i]]是初始值则跳过
                    dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

拓展

对于遍历方式target放在外循环coins在内循环都是可以的只不过对应的初始化操作有点微调我就直接给出代码了

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount + 1, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.size(); j++) {
                if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX ) {
                    dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};

总结

相信大家看网上的题解一篇是遍历amount的for循环放外面一篇是遍历amount的for循环放里面看多了都看晕了能把 遍历顺序讲明白的文章非常少。

这也是大多数同学学习动态规划的苦恼所在,有的时候递推公式其实很简单,但遍历顺序很难把握!

那么Carl就把遍历顺序分析的清清楚楚相信大家看完之后对背包问题又了更深的理解了。

tmp

// dp初始化很重要
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //int dp[10003] = {0}; // 并没有给所有元素赋值0
        if (amount == 0) return 0; // 这个要注意
        vector<int> dp(10003, 0);
        // 不能这么初始化啊,[2147483647]2 这种例子 直接gg但是这种初始化有助于理解
        for (int i = 0; i < coins.size(); i++) {
            if (coins[i] <= amount) // 还必须要加这个判断
            dp[coins[i]] = 1;
        }
        for (int i = 1; i <= amount; i++) {
            for (int j = 0; j < coins.size(); j++) {
                if (i - coins[j] >= 0 && dp[i - coins[j]]!=0 ) {
                    if (dp[i] == 0) dp[i] = dp[i - coins[j]] + 1;
                    else dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
            //for (int k = 0 ; k<= amount; k++) {
            //    cout << dp[k] << " ";
            //}
            //cout << endl;
        }
        if (dp[amount] == 0) return -1;
        return dp[amount];

    }
};
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        //int dp[10003] = {0}; // 并没有给所有元素赋值0
        if (amount == 0) return 0; // 这个要注意
        vector<int> dp(amount + 1, 0);
        for (int i = 1; i <= amount; i++) {
            dp[i] = INT_MAX;
            for (int j = 0; j < coins.size(); j++) {
                if (i - coins[j] >= 0 && dp[i - coins[j]] != INT_MAX) {
                    dp[i] = min(dp[i - coins[j]] + 1, dp[i]);
                }
            }
        }
        if (dp[amount] == INT_MAX) return -1;
        return dp[amount];
    }
};