1 题目
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:
输入:coins = [2], amount = 3
输出:-1
示例 3:
输入:coins = [1], amount = 0
输出:0
提示:
1 <= coins.length <= 121 <= coins[i] <= 231 - 10 <= amount <= 104
2 代码实现
c++
cpp
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1 , amount + 1) ;
dp[0] = 0 ;
for (int i = 1 ; i <= amount ; i++ ){
for(int coin : coins){
if (i >= coin ){
dp[i] = min( dp[i] , dp [i - coin] + 1 );
}
}
}
return dp[amount] > amount ? -1 : dp[amount] ;
}
};
js
javascript
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
const dp = new Array(amount + 1 ).fill(amount + 1 );
dp[0] = 0 ;
for ( let i = 0 ; i <= amount ; i ++){
for(const coin of coins){
if (i >= coin){
dp[i] = Math.min(dp[i] , dp [i - coin ] + 1 );
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
};
思考
算法猪头也来做背包问题了,续上昨天所写的dp。
其实是偷偷看了题解的,自己写一遍思路。
题解
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount + 1 , amount + 1) ;
//首先初始化数组,amount + 1 是不可能达到的,因为coins最小的面值是1
dp[0] = 0 ;//定义dp[i] = i ,意思是要凑上i元钱,所需要dp[i]枚coins
for (int i = 1 ; i <= amount ; i++ ){
for(int coin : coins){
if (i >= coin ){
// 循环条件,当前所需要凑i元钱,硬币面值是比当前要凑的钱小的,因为5元硬币不可能凑成2元
dp[i] = min( dp[i] , dp [i - coin] + 1 );
// 之前已经凑了i 元,新的是 i- coin 加上当前这个coin的面值,也就是min动态更新一下当前凑上i元所需要最小的个数
}
}
}
return dp[amount] > amount ? -1 : dp[amount] ;
//如果要凑amount元,所需要的硬币个数会大于amount吗?如果是的,说明还是我们一开始初始化的amount + 1 ,诶!根本就没更新,所以就是凑不到的! return -1 ,不行 ! 否则就是可以滴~
}
};
思路
- dp [i] = 凑 i 元最少硬币数
- 对每个金额,试每一种硬币
- 能用上硬币就更新:dp [i] = min (dp [i], dp [i-coin]+1)
- dp[0] = 0
3 题目
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s 则返回 true。
**注意:**不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"]
输出: true
解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。
示例 2:
输入: s = "applepenapple", wordDict = ["apple", "pen"]
输出: true
解释: 返回 true 因为 "applepenapple" 可以由 "apple" "pen" "apple" 拼接成。
注意,你可以重复使用字典中的单词。
示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
输出: false
提示:
1 <= s.length <= 3001 <= wordDict.length <= 10001 <= wordDict[i].length <= 20s和wordDict[i]仅由小写英文字母组成wordDict中的所有字符串 互不相同
4 代码实现
c++
cpp
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
int n = s.size() ;
vector <bool> dp(n + 1, false );
dp[0] = true ;
for ( int i = 1 ; i <= n ; i ++ ){
for (string &word : wordDict){
int len = word.size();
if ( i >= len && dp [i - len] && s.substr(i - len, len) == word){
dp[i] = true;
break;
}
}
}
return dp[n];
}
};
js
javascript
/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/
var wordBreak = function(s, wordDict) {
const n = s.length ;
const dp = new Array(n + 1 ).fill(false);
dp[0] = true ;
for (let i = 1 ; i <= n ; i ++ ){
for (const word of wordDict){
const len = word.length ;
if (i >= len && dp [i - len] && s.slice (i - len,i) === word ){
dp[i] = true ;
break ;
}
}
}
return dp[n] ;
};
思考
这啥啊!!!我觉得是呃,考察一下字符串拼接,呃然后呢,这下要怎么办呢。。。。。。双层循环吗,为什么能重复用那么复杂度不会很高吗,我每次又回头来一直验证是否可以达到是我目标中的字符串?
题解
dp [i] = 字符串前 i 个字符能不能拼出来
类型:true / false
比如:
dp[0] = true→ 0 个字符,空串,一定能拼出来dp[4] = true→ 前 4 个字符能拼出来dp[8] = true→ 整个串能拼出来
要判断 dp[i] 能不能拼出来,只需要看:有没有一个单词 word,使得:
- 前 i - len (word) 能拼出来 →
dp[i - len] = true - 最后这一段正好等于 word →
s 里截取的子串 == word
只要有一个满足 ,dp[i] = true
dp[i] = dp[i - len] && (s.substr(i - len, len) == word)
dp[0] = true
空串一定能拼出来!
dp[s.length]
"可以重复用,复杂度很高吗?"
不高!因为我们用 dp 记录了前面哪些位置能到达不会重复计算!这就是动态规划的意义!
javascript
var wordBreak = function(s, wordDict) {
const n = s.length;
const dp = new Array(n + 1).fill(false);
dp[0] = true; // 空串可以拼成
for (let i = 1; i <= n; i++) { // 遍历所有长度
for (const word of wordDict) { // 试每个单词
const len = word.length;
if (i >= len && dp[i - len] && s.slice(i - len, i) === word) {
dp[i] = true;
break;
}
}
}
return dp[n];
};
比如 s = "leetcode"字典 ["leet","code"]
- dp[0] = true
- i=4,试单词 "leet"dp [4-4] = dp [0] = true子串 == "leet" → dp [4] = true
- i=8,试单词 "code"dp [8-4] = dp [4] = true子串 == "code" → dp [8] = true
最终返回 true ✅
这两道题:
- 零钱兑换 = 无限硬币凑钱
- 单词拆分 = 无限单词凑字符串
5 小结
难以置信我有种自己行了的感觉!
第一个我已经扎扎实实写了一遍,第二个我发现了多了slice 或者 substr的转换,其实是一样的。啊啊啊!!!
- 都是无限使用物品
- 都是从前向后推导
- 都是看最后一步能不能接上
- 都是双层循环
- 一个求最小数量,一个判断能否拼成
坚持,加油,定期复盘一下,重要的是要记住思路!!!!