279.完全平方数
给你一个整数 n
,返回 和为 n
的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1
、4
、9
和 16
都是完全平方数,而 3
和 11
不是。
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-i*i]+1,dp[j]);
}
}
return dp[n];
}
};
1.简单的完全背包问题
1.数字可使用无数次 完全背包
2.求的是组合数 先遍历物品 在遍历背包
3.此时物品的重量为i*i
for(int i=1;i*i<=n;i++){
for(int j=i*i;j<=n;j++)
所以此时 遍历物品时 I*I<=n
遍历背包时 j=i*i
139 单词拆分(动态规划,哈希表)
给你一个字符串 s
和一个字符串列表 wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出 s
则返回 true
。(由此可见dp数组应该为bool型)
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
注意:本题求的是排列数 所以外部遍历背包,内部遍历物品
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); //substr(起始位置,截取的个数)
if (wordSet.find(word) != wordSet.end() && dp[j]) {
dp[i] = true;
}
}
}
return dp[s.size()];
}
};
1.unordered_set
关联容器
1.unordered_set
是一种关联容器,用于存储唯一的元素,并且可以快速检索元素。它内部使用哈希表实现,因此查找、插入和删除操作的平均时间复杂度是 O(1)。
2.wordSet
包含了 wordDict
中的所有单词,但是因为没有重复的元素,所以 wordSet
可能会比 wordDict
少一些元素。
使用 unordered_set
而不是 vector
来存储字典的好处是,可以更快地检查一个单词是否在字典中,因为 unordered_set
提供了常数时间复杂度的查找操作。
在 wordBreak
函数中,使用 wordSet
来检查一个子串是否是字典中的一个单词,这样可以提高算法的效率。
2.for (int j = 0; j < i; j++)
for (int j = 0; j < i; j++)
是一个循环语句,它用于遍历字符串 s
的前 i
个字符的所有可能的分割点。
这里的 i
是当前正在考虑的子字符串的结束位置(不包括 i
本身,因为字符串索引是从 0 开始的)。j
是分割点的位置,即子字符串的起始位置。
循环的目的是检查从索引 0
到 i-1
的每一个位置 j
,将字符串 s
分割成两个部分:s[0...j-1]
和 s[j...i-1]
。然后,代码会检查以下几个条件:
s[j...i-1]
是否是字典wordSet
中的一个单词。s[0...j-1]
是否可以由字典中的单词组成(即dp[j]
是否为true
)。
如果这两个条件都满足,那么 s[0...i-1]
也可以由字典中的单词组成,因此将 dp[i]
设置为 true
。
这个循环是动态规划算法的一部分,它帮助构建一个布尔数组 dp
,其中 dp[i]
表示字符串 s
的前 i
个字符是否可以由字典中的单词组成。通过遍历所有可能的分割点,算法可以找到所有可能的单词组合,从而解决问题。
3.对象.substr
substr
是 C++ STL 中 string
类的一个成员函数,用于获取字符串的子串。这个函数有两个参数:
- 第一个参数是子串的起始位置,也就是从字符串的哪个位置开始截取。
- 第二个参数是子串的长度,也就是要截取多少个字符。
string word = s.substr(j, i - j);
s.substr(j, i - j)
就会从位置 j
开始,截取长度为 i - j
的子串。
4.对象.find(要查找的对象)
-
wordSet.find(word) != wordSet.end()
:这个条件检查子串word
是否在字典wordSet
中。wordSet
是一个unordered_set
,它存储了所有可以用来分割字符串的单词。 -
find
函数是unordered_set
的一个成员函数,用于在集合中查找一个元素。如果找到了元素,find
函数返回指向该元素的迭代器;如果没有找到,返回end()
方法返回的迭代器。因此, //*如果find(word)
不等于end()
,这意味着word
在字典中。//
5.dp[j]的作用
-
dp[j]
:这个条件检查s
的前j
个字符是否可以被分割成字典中的单词。dp
是一个布尔类型的向量,其中dp[j]
的值表示s
的前j
个字符是否可以被分割。如果dp[j]
是true
,那么s
的前j
个字符可以被分割成字典中的单词。
6.unordered_set与unordered_map的区别
unordered_set
和 unordered_map
都是 C++ STL(标准模板库)中的关联容器,它们都基于哈希表实现,提供快速的查找、插入和删除操作。二者的主要区别在于它们存储的数据类型和用途。
-
unordered_set
:unordered_set
是一个集合容器,用于存储唯一的元素。- 它内部通过哈希表来存储元素,这意味着它可以在平均常数时间 O(1) 内进行元素的查找、插入和删除操作。
unordered_set
只存储键(key),不存储值(value),每个元素都是唯一的。- 它通常用于检查一个元素是否存在于集合中,或者用于去除重复元素。
-
unordered_map
:unordered_map
是一个键值对映射容器,用于存储键和值的组合。- 它也通过哈希表来存储键值对,提供快速的查找、插入和删除操作。
unordered_map
存储的是键(key)和对应的值(value),键是唯一的,而值可以重复。- 它通常用于根据键快速查找对应的值,或者用于实现映射关系。
总结来说,unordered_set
用于存储无重复的元素集合,而 unordered_map
用于存储键值对的映射。选择使用哪个容器取决于你的具体需求:如果你只需要存储元素并快速判断元素是否存在,使用 unordered_set
;如果你需要存储键值对并快速通过键来查找值,使用 unordered_map
。
198.打家劫舍(动态规划)
***求最大值时dp数组初始化为INT_MIN***
***求最小值时dp数组初始化为INT_MAN***
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0) return 0;
if (nums.size() == 1) return nums[0];
vector<int> dp(nums.size()+1,INT_MIN);
dp[0]=nums[0];
dp[1] = max(nums[0], nums[1]);
for(int i=2;i<nums.size();i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[nums.size()-1];
}
};
1.dp[i]=max(dp[i-2]+nums[i],dp[i-1]);的意义
dp[i-2] + nums[i]
:这意味着小偷偷窃了当前房屋i
,因此不能偷窃前一个房屋i-1
,但可以加上偷窃房屋i-2
之前的最高金额。dp[i-1]
:这意味着小偷选择不偷窃当前房屋i
,因此他的总金额保持为偷窃房屋i-1
时的最高金额。
在循环中,dp
数组被逐步填充,每个 dp[i]
都是基于之前的计算结果。最后,dp[nums.size()-1]
就是小偷能够偷窃到的最高金额,因为它表示到达最后一个房屋时能够偷窃到的最高金额。
***注意,代码中的 dp
数组的大小被设置为 nums.size()+1***
,并且初始化为 INT_MIN
。
这是为了处理边界情况,
dp[0]
被设置为 nums[0]
,表示如果只有一个房屋,那么偷窃它的金额就是最高金额。
dp[1]
被设置为 max(nums[0], nums[1])
,表示如果只有两个房屋,小偷会选择金额较多的那个房屋进行偷窃。
213.打家劫舍2(动态规划)
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,今晚能够偷窃到的最高金额。
class Solution {
public:
int rob(vector<int>& nums) {
if (nums.size() == 0)
return 0;
if (nums.size() == 1)
return nums[0];
int result1 = robResult(nums, 0, nums.size() - 2);
int result2 = robResult(nums, 1, nums.size() - 1);
return max(result1, result2);
}
int robResult(vector<int>& nums, int begin, int end) {
if (begin == end)
return nums[begin];
vector<int> dp(nums.size(), INT_MIN);
dp[begin] = nums[begin];
dp[begin + 1] = max(nums[begin], nums[begin + 1]);
for (int i = begin+2; i <= end; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
};
1.首尾房屋相邻时,分情况讨论
1.int result1 = robResult(nums, 0, nums.size() - 2); 情况二
int result2 = robResult(nums, 1, nums.size() - 1); 情况三
对这两种情况进行分类讨论 取最大值(最合适的)
2.分类讨论的两种情况都继续使用打家劫舍的方法进行实现
int robResult(vector<int>& nums, int begin, int end) {
if (begin == end)
return nums[begin];
vector<int> dp(nums.size(), INT_MIN);
dp[begin] = nums[begin];
dp[begin + 1] = max(nums[begin], nums[begin + 1]);
for (int i = begin+2; i <= end; i++) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
}
return dp[end];
}
};
注意此时 for循环内 i开始的条件变成了begin+2, 限定条件变成了i<=end