题目链接: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。
物品为:
重量 价值 数量 物品0 1 15 2 物品1 3 20 3 物品2 4 30 2 问背包能背的物品最大价值是多少?
上述情况完全等同于如下情况:由多重背包问题转成了 01 背包问题,每个物品只用一次。
重量 价值 数量 物品0 1 15 1 物品0 1 15 1 物品1 3 20 1 物品1 3 20 1 物品1 3 20 1 物品2 4 30 1 物品2 4 30 1
5. 总结
(待更新...)
相关题目和后续提高: