322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
- 输入:coins = [1, 2, 5], amount = 11
- 输出:3
- 解释:11 = 5 + 5 + 1
示例 2:
- 输入:coins = [2], amount = 3
- 输出:-1
注意**:
要让dp数组的下标有意义!注意有减法的部分
当递归公式涉及到最小值的选取(eg:实现背包装满的最少物品的数量)!初始化的时候,其他要设置成intmax,防止fmin的时候出错
int coinChange(int* coins, int coinsSize, int amount) {
int dp[amount+1];
for (int j=0;j<=amount;j++){
dp[j]=INT_MAX;
}
dp[0]=0;
for (int i=0;i<coinsSize;i++){
for(int j=0;j<=amount;j++){
if(j<coins[i] || dp[j-coins[i]]==INT_MAX) continue;
dp[j]=fmin(dp[j],dp[j-coins[i]]+1);
printf("%d ",dp[j]);
}
printf("\n");
}
if(dp[amount]==INT_MAX) return -1;
return dp[amount];
}
279.完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
给你一个整数 n ,返回和为 n 的完全平方数的 最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
示例 1:
- 输入:n = 12
- 输出:3
- 解释:12 = 4 + 4 + 4
示例 2:
- 输入:n = 13
- 输出:2
- 解释:13 = 4 + 9
int numSquares(int n) {
int dp[n+1];
int j;
for (j=0;j<=n;j++){
dp[j]=INT_MAX;
}
dp[0]=0;
for (int i=1;i*i <=n;i++){
for (j=0;j<=n;j++){
if(j<i*i || dp[j-i*i] == INT_MAX) continue;
dp[j]=fmin(dp[j], dp[j-i*i] +1);
}
}
return dp[n];
}
139.单词拆分
给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
- 输入: s = "leetcode", wordDict = ["leet", "code"]
- 输出: true
- 解释: 返回 true 因为 "leetcode" 可以被拆分成 "leet code"。
示例 2:
- 输入: s = "applepenapple", wordDict = ["apple", "pen"]
- 输出: true
- 解释: 返回 true 因为 "applepenapple" 可以被拆分成 "apple pen apple"。
- 注意你可以重复使用字典中的单词。
分析:
单词就是物品,字符串s就是背包,单词能否组成字符串s,就是问物品能不能把背包装满。
拆分时可以重复使用字典中的单词,说明就是一个完全背包!
确定dp数组以及下标的含义
dp[i] : 字符串长度为i的话,dp[i]为true,表示可以拆分为一个或多个在字典中出现的单词。
确定递推公式
如果确定dp[j] 是true,且 [j, i] 这个区间的子串出现在字典里,那么dp[i]一定是true。(j < i )。
所以递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] = true。
dp数组如何初始化
从递推公式中可以看出,dp[i] 的状态依靠 dp[j]是否为true,那么dp[0]就是递推的根基,dp[0]一定要为true,否则递推下去后面都都是false了。
dp[0]初始为true完全就是为了推导公式。
下标非0的dp[i]初始化为false,只要没有被覆盖说明都是不可拆分为一个或多个在字典中出现的单词。
确定遍历顺序
题目中说是拆分为一个或多个在字典中出现的单词,所以这是完全背包。
还要讨论两层for循环的前后顺序。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
而本题其实我们求的是排列数,为什么呢。 拿 s = "applepenapple", wordDict = ["apple", "pen"] 举例。
"apple", "pen" 是物品,那么我们要求 物品的组合一定是 "apple" + "pen" + "apple" 才能组成 "applepenapple"。
"apple" + "apple" + "pen" 或者 "pen" + "apple" + "apple" 是不可以的,那么我们就是强调物品之间顺序。
所以说,本题一定是 先遍历 背包,再遍历物品。
——看排列交换顺序,是否影响本题结果!
bool wordBreak(char* s, char** wordDict, int wordDictSize) {
int len=strlen(s);
bool dp[len+1];//在第j个字母时,是否能全部填充上?所以是从0开始,到len,len+1不取
int i,j;
memset(dp, false, sizeof (dp));
dp[0]=true;
for (j=1;j<=len;j++){
for(i=0;i<wordDictSize;i++){
int wordLen = strlen(wordDict[i]);
// 分割点是由i和字典单词长度决定
int k = j - wordLen;
if(k < 0){
continue;
}
// 这里注意要限制长度,故用strncmp
dp[j] = (dp[k] && !strncmp(s + k, wordDict[i], wordLen)) || dp[j];
//字符串切片用strncmp
}
}
return dp[len];
}