代码随想录算法训练营33天 | ​​322. 零钱兑换、279.完全平方数、139.单词拆分、多重背包理论基础

题目链接:322. 零钱兑换279.完全平方数139.单词拆分多重背包理论基础
文章链接:代码随想录

动态规划


1. 零钱兑换

1. 确定dp数组(dp table)以及下标的含义:dp[j] 表示凑足总额 j 所需钱币的最少个数为dp[j]

2. 确定递推公式:dp[j] = min(dp[j], dp[j - coins[i]] + 1)

推导:凑足总额为 j - coins[i] 的最少个数为 dp[j - coins[i]],那么只需要加上一个钱币 coins[i]即就是 dp[j](考虑 coins[i]),同时 dp[j] 要取所有 dp[j - coins[i]] + 1 中最小的

3. dp数组如何初始化:根据递推公式,首先凑足总金额为 0 所需钱币的个数一定是 0,即 dp[0] = 0,对非 0 下标的元素,由于递推过程中 dp[j] 比较的是较小值,因此初始化值应该为最大值,避免比较过程中被初始值覆盖

4. 确定遍历顺序:本题求钱币最小个数,那么钱币有顺序和没有顺序都可以,都不影响钱币的最小个数

5. 举例推导dp数组:

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++) {
                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];
    }
};

 2. 完全平方数

//本题和“322. 零钱兑换”思路完全一致

class Solution {
public:
    int numSquares(int n) {
        vector<int> dp(n + 1, INT_MAX);
        dp[0] = 0;

        vector<int> nums(101);
        for(int i = 1; i < nums.size(); i++) {
            nums[i] = i * i;
        }

        for(int i = 1; i < nums.size(); i++) {
            for(int j = nums[i]; j <= n; j++) {
                dp[j] = min(dp[j - nums[i]] + 1, dp[j]);
            }
        }

        return dp[n];
    }
};

3. 单词拆分

1. 确定dp数组(dp table)以及下标的含义:dp[j] = true 表示长度为 j 的字符串可以拆分为一个或多个在字典中出现的单词

2. 确定递推公式:如果 dp[j] = true,且 [j, i] 这个区间的子串出现在字典里,那么 dp[i] 一定是 true( j < i )

3. dp数组如何初始化:根据递推公式,dp[i] 的状态依靠 dp[j] 是否为 true,因此 dp[0] 就是递推的根基,dp[0] 一定是true,否则递推下去后面都是false了

dp[0] 表示如果字符串为空则说明出现在字典里,但题目中说了“给定一个非空字符串”,所以测试数据中不会出现 i = 0 的情况,因此 dp[0] 初始为 true 完全是为了推导公式

下标非 0 的 dp[i] 则全部初始化为 false,只要没有被覆盖,就说明都是不可拆分为一个或多个在字典中出现的单词

4. 确定遍历顺序:本题实际上求的是排列数,具体原因参考为何求的是排列数?

5. 举例推导dp数组:

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> wordSet(wordDict.begin(), wordDict.end());
        vector<bool> dp(s.size() + 1, false);
        dp[0] = true;

        /*
            核心思路是:
            dp[i]表示前i个字符(s[0..i-1])能否被字典里的单词拆分
            遍历所有可能的拆分点j(0 ≤ j < i),看看[j..i-1]这段子串是不是字典里的单词
            如果是并且前半部分(dp[j])也能拆分,就让 dp[i] = true
        */
        for(int i = 1; i <= s.size(); i++) {
            for(int j = 0; j < i; j++) {
                //substr(起始位置, 截取长度)表示从下标j开始,截取长度为(i - j)的子串,也就是s[j..i-1]这一段
                string word = s.substr(j, i - j);
                if(dp[j] && wordSet.find(word) != wordSet.end())
                    dp[i] = true;
            }
        }

        return dp[s.size()];
    }
};

4. 多重背包理论基础

有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 Mi 件可用,每件耗费的空间是 Ci ,价值是 Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。

多重背包和 01 背包是非常像的,每件物品最多有 Mi 件可用,把 Mi 件摊开,其实就是一个 01 背包问题了。

例如:背包最大重量为10。

物品为:

重量价值数量
物品01152
物品13203
物品24302

问背包能背的物品最大价值是多少?

上述情况完全等同于如下情况:由多重背包问题转成了 01 背包问题,每个物品只用一次。

重量价值数量
物品01151
物品01151
物品13201
物品13201
物品13201
物品24301
物品24301

5. 总结

(待更新...)


 相关题目和后续提高:


心得:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值