文章参考来源代码随想录
322. 零钱兑换
无限物品,完全背包
动规五部曲:
1.确定dp数组以及下标含义:
凑足总额为j所需钱币的最少个数为dp[j]
2.确定递推公式:
凑够j-coins[i]最少要dp[j-coins[i]],所以dp[j]就是dp[j-coins[i]]+1
dp[j]要取所有dp[j-coins[i]]+1中的最小值
所以: dp[j]=min(dp[j],dp[j-coins[i]]+1)
3.dp如何初始化:
由递推公式知,dp取的是最小,为了方便后续的更新,这里初始化为最大值
此外,下标0处应该初始化为0,因为凑足面额0只能有0个钱币
4.确定遍历顺序:
完全背包,且没有组合排列之分
这里先物品再背包或者反一下都行
5.举例推导dp数组:
以输入:coins = [1, 2, 5], amount = 5为例
dp[amount]为最终结果。
需要注意的点:
在内层循环中药判断待使用的dp[j-coins[i]]是否为INT_MAX,因为后续要+1,这里先判断以防越界
如果最终结果为INT_MAX,说明找不出最小方案,输出-1
代码:
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]=min(dp[j],dp[j-coins[i]]+1);
}
}
if(dp[amount]==INT_MAX)return -1;
return dp[amount];
}
};
先背包后物品版本:
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];
}
};
279.完全平方数
与上一题类似
动规五部曲:
1.确定dp数组以及下标含义:
组成j的完全平方数最少为dp[j]
2.确定递推公式:
组成j-i*i最少要dp[j-i*i]个,因此组成dp[j]就要dp[j-i*i]+1个
dp[j]取上述最小值
所以 dp[j]=min(dp[j],dp[j-i*i]+1)
3.dp如何初始化:
要取最小就要初始化为最大
此外组成0的完全平方数为0个
4.确定遍历顺序:
完全背包,且排列组合之分,先物品后背包反之都行
5.举例推导dp数组:
已输入n为5例,dp状态图如下:
dp[0] = 0 dp[1] = min(dp[0] + 1) = 1 dp[2] = min(dp[1] + 1) = 2 dp[3] = min(dp[2] + 1) = 3 dp[4] = min(dp[3] + 1, dp[0] + 1) = 1 dp[5] = min(dp[4] + 1, dp[1] + 1) = 2
最后的dp[n]为最终结果。
需要注意的点:
要在循环中加入判断n是不是完全平方数的语句:i*i<=n
代码:
class Solution {
public:
int numSquares(int n) {
vector<int>dp(n+1,INT_MAX);
dp[0]=0;
for(int i=1;i*i<=n;i++){
for(int j=i*i;j<=n;j++){
dp[j]=min(dp[j],dp[j-i*i]+1);
}
}
return dp[n];
}
};
先背包后物品版本:
class Solution {
public:
int numSquares(int n) {
vector<int> dp(n + 1, INT_MAX);
dp[0] = 0;
for (int i = 0; i <= n; i++) { // 遍历背包
for (int j = 1; j * j <= i; j++) { // 遍历物品
dp[i] = min(dp[i - j * j] + 1, dp[i]);
}
}
return dp[n];
}
};
139.单词拆分
动规五部曲:
1.确定dp数组以及下标含义:
字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
2.确定递推公式:
当字符串长度为j的dp[j]为true时,判断j到i部分是否可以被拆分,可以的话,则dp[i]也为true
所以,dp[j]==true&&j到i部分可以被拆分,则dp[i]为true
3.dp如何初始化:
下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。
dp[0]因为无意义(题中说了字符串不为空),都是为了后续更新,初始化为true
4.确定遍历顺序:
完全背包,且本质上是排列问题
具体分析:
"apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。
"apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。
所以本题先背包后物品
5.举例推导dp数组:
以输入: s = "leetcode", wordDict = ["leet", "code"]为例,dp状态如图:
dp[s.size()]就是最终结果。
需要注意的点:
find函数使用前要先将vector转化为set
在先背包后物品中,一般背包从1开始,到最大容量,物品从0号开始,到当前背包容量(类)或者物品总数,不过还是要具体分析
代码:
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;
for(int i=1;i<=s.size();i++){
for(int j=0;j<i;j++){
string word=s.substr(j,i-j);
if(dp[j]&&wordset.find(word)!=wordset.end())dp[i]=true;
}
}
return dp[s.size()];
}
};