day38 力扣279.完全平方数 力扣322. 零钱兑换 力扣139.单词拆分
完全平方数
给你一个整数
n
,返回 和为n
的完全平方数的最少数量 。完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,
1
、4
、9
和16
都是完全平方数,而3
和11
不是。示例 1:
输入:n =12
输出:3 解释:12 = 4 + 4 + 4
示例 2:
输入:n =13
输出:2 解释:13 = 4 + 9
提示:
1 <= n <= 104
一看可以重复使用,那就是完全背包的问题,后续外层for循环遍历背包容量,内层遍历物品。
因为本题所求的是最少的完全平方数量,dp[j]表示容量为j的背包需要的最少完全平方数量。
那么,我们在初始化的时候,需要把dp初始化的尽可能大,也就是每个dp[j]=j(即每个都是由j个1*1组成)。
还有一个问题,就是遍历的物品是什么呢? 等同于j嘛? 其实并不是,i<=sqrt(j)也就是根号下容量才是我们的物品,因为我们所求的是完全平方。
如果当前容量j>=i*i,那我们就让dp[j]等于 dp[j]和 dp[j-i*i]+1的最小值。
class Solution {
public:int numSquares(int n) {vector<int> dp(n+1);for(int i = 0;i <= n;i++){dp[i] = i;}for(int j = 0;j<=n;j++){for(int i = 1;i<=sqrt(n);i++){if(j>=i*i) dp[j] = min(dp[j],dp[j-i*i]+1);}}return dp[n];}
};
零钱兑换
给你一个整数数组
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 <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104
其实这道题应该是在完全平方数前面的,但是我做反了······
但我觉得这道题比完全平方数更难(因为完全平方数我做出来了,这道题自己没做出来)。
这道题我认为比较难的地方就是初始化,首先都初始化为-1,我的想法是,两层循环,外层遍历物品,内层遍历背包,只考虑使用同一种物品,如果能塞满背包,dp[j] = j/coins[i]。
得到如下代码:
class Solution {
public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount+1,-1);for(int i = 0;i<coins.size();i++){for(int j = 0;j<=amount;j++){if(j%coins[i]==0&&dp[j]!=-1) dp[j] =min(dp[j],j/coins[0]);else if(j%coins[i]==0&&dp[j]==-1) dp[j] =j/coins[0];}}for(int j = 0;j<=amount;j++){for(int i = 0;i<coins.size();i++){if(j>=coins[i]&&dp[j-coins[i]!=-1]) dp[j] = min(dp[j],dp[j-coins[i]]+1);}}return dp[amount];}
};
但是并不太可取(WA,我不知道怎么改了)。
下面给出题解方法:
在初始化时,我们初始化为INT_MAX,dp[0]=0。
循环内,如果dp[j-coins[i]] == INT_MAX就不处理,反之就让dp[j] = min(dp[j],dp[j-coins[i]]+1);
最后如果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 j = 1;j<=amount;j++){for(int i = 0;i<coins.size();i++){if(j>=coins[i]&&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];}
};
单词拆分
给你一个字符串
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 <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅由小写英文字母组成wordDict
中的所有字符串 互不相同
一看可以重复使用,那就是完全背包的问题,后续外层for循环遍历背包容量,内层遍历物品。
dp[j]表示s从0到j位置的子字符串是否可以用字典符里的word表示。
初始化dp[0]为true,其他均为false。
j表示容量,i在0和j之间,如果i到j的字符串在字典中出现,且dp[i]==true,那dp[j]也是true。
class Solution {
public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool> dp(s.size()+1,false);unordered_set<string> wordSet(wordDict.begin(),wordDict.end());dp[0] = true;for(int j = 1;j<=s.size();j++){for(int i = 0;i<j;i++){string word = s.substr(i,j-i);if(wordSet.find(word)!=wordSet.end()&&dp[i])dp[j] = true;}}return dp[s.size()];}
};
将 wordDict 转换为 unordered_set 是为了提高查找效率。 unordered_set 内部使用哈希表实现,平均时间复杂度为 O(1) 进行元素的插入、删除和查找操作。而 vector 的查找操作(例如使用 std::find )需要遍历整个 vector ,平均时间复杂度为 O(n),其中 n 是 vector 的大小。在 wordBreak 这种需要频繁查找单词的场景中,使用 unordered_set 可以显著提升性能。