新年初五,刷题也懈怠了不少,今晚总算是刷完了代码随想录的所有章节(除了额外题目)
在做贪心题目有看到动态规划的解法但是并不理解,我也以为会很难理解,但真的去了解后才发现,有些题目确实不好理解,但画画图多花点时间还是能懂个七七八八
随笔
int n=3;
while(n–)执行3次
int n=3;
while(–n)执行2次
背包问题
dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,(这里的容量j为背包还能够容纳的物品的重量)价值总和最大是多少
若物品i放不进去/不放进去:由dp[i-1][j-0]推出
若物品i放得进去,由dp[i-1][j-weight[i]]+value[i]推出
dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值
分割等和子集
dp[j] == j 说明,集合中的子集总和正好可以凑成总和j
dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]
相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]
目标和
确定nums[i]),凑成dp[j]就有dp[j - nums[i]] 种方法
凑整dp[5]有多少方法呢,也就是把 所有的 dp[j - nums[i]] 累加起来
dp[j] += dp[j - nums[i]]
一和零
dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]
dp[i][j]=max(dp[i][j],dp[i-zeroNum][j-onrNum]+1);
完全背包
每件物品都有无限个(也就是可以放入背包多次)
第i件物品的重量是weight[i],得到的价值是value[i]
01背包中二维dp数组的两个for遍历的先后循序可以颠倒,一维dp数组的两个for循环先后循序一定是先遍历物品,再遍历背包容量。
在完全背包中,对于一维dp数组来说,其实两个for循环嵌套顺序是无所谓的!
01背包内嵌的循环是从大到小遍历,为了保证每个物品仅被添加一次
完全背包的物品是可以添加多次的,所以要从小到大去遍历
组合不强调元素之间的顺序,排列强调元素之间的顺序
零钱兑换||
dp[j]:凑成总金额j的货币组合数为dp[j]
dp[j]+=dp[j-coins[i]];
如果求组合数就是外层for循环遍历物品,内层for遍历背包
如果求排列数就是外层for遍历背包,内层for循环遍历物品
组合总和 Ⅳ(实际是排列问题)
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
vector<int> dp(target+1,0);
dp[0]=1;
for(int j=0;j<=target;j++){
for(int i=0;i<nums.size();i++){//不能写j=nums[i],因为这里的i还没有初始化
if(j-nums[i]>=0&&dp[j]<INT_MAX-dp[j-nums[i]]){
//C++测试用例有两个数相加超过int的数据,所以需要在if里加上dp[i] < INT_MAX - dp[i - num]。
dp[j]+=dp[j-nums[i]];
}
}
}
return dp[target];
}
};
零钱兑换
求凑成总金额所需的最少的硬币个数
先物品或先背包都可以
完全平方数
dp[j]:和为j的完全平方数的最少数量为dp[j]
先物品或先背包都可以
单词拆分
可以aab为例子,a,aa为子字符串
class Solution {
private:
bool backtracking(const string&s,
const unordered_set<string>& wordSet,
vector<bool>& memory,
int startIndex){
if(startIndex>=s.size()){
return true;
}
if(!memory[startIndex])return memory[startIndex];
for(int i=startIndex;i<s.size();i++){
string word =s.substr(startIndex,i-startIndex+1);
if(wordSet.find(word)!=wordSet.end()&&backtracking(s,wordSet,memory, i+1))return true;
}
memory[startIndex]=false;
return false;
}
public:
bool wordBreak(string s, vector<string>& wordDict) {
unordered_set<string> wordSet(wordDict.begin(),wordDict.end());//数值即字符串不可以重复
vector<bool> memory(s.size(),1);
return backtracking(s,wordSet,memory, 0);
}
};
dp[i]:长度为i,为true时可拆分为字典的字符串
本题强调物品之间的顺序,所以使用排列,先遍历背包,再遍历物品
强调组合的话先遍历物品再遍历背包
多重背包
等价于多个01背包
打家劫舍——dp[i]偷到第i家所能偷到的最大金额
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
打家劫舍II——dp[i]偷到第i家所能偷到的最大金额
考虑去首与去尾两种情况
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size()==0)return 0;
if(nums.size()==1)return nums[0];
if(nums.size()==2)return max(nums[0],nums[1]);
int result1=robRange(nums,0,nums.size()-2);
int result2=robRange(nums,1,nums.size()-1);
return max(result1,result2);
}
int robRange(vector<int>& nums,int start,int end){
//if(start==end)return nums[start];
vector<int> dp(nums.size());
dp[start]=nums[start];
dp[start+1]=max(nums[start],nums[start + 1]);
for(int i=start + 2;i<=end;i++){
dp[i]=max(dp[i-2]+nums[i],dp[i-1]);
}
return dp[end];
}
};
打家劫舍 III——1.递归重复计算超时
class Solution {
public:
unordered_map<TreeNode*,int>umap;
int rob(TreeNode* root) {
if(root==NULL)return 0;
if(root->left==NULL&&root->right==NULL)return root->val;
int val1=root->val;
if(root->left)val1+=rob(root->left->left)+rob(root->left->right);
if(root->right)val1+=rob(root->right->left)+rob(root->right->right);
int val2=rob(root->left)+rob(root->right);
return max(val1,val2);
}
};
2.记忆化递推
class Solution {
public:
unordered_map<TreeNode*,int>umap;
int rob(TreeNode* root) {
if(root==NULL)return 0;
if(root->left==NULL&&root->right==NULL)return root->val;
if(umap[root])return umap[root];
int val1=root->val;
if(root->left)val1+=rob(root->left->left)+rob(root->left->right);
if(root->right)val1+=rob(root->right->left)+rob(root->right->right);
int val2=rob(root->left)+rob(root->right);
umap[root]=max(val1,val2);
return max(val1,val2);
}
};
3.dp数组:下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int rob(TreeNode* root) {//二维数组,不取该点与取该点取得的最大金钱
vector<int> result=robTree(root);
return max(result[0],result[1]);
}
vector<int> robTree(TreeNode* cur){
if(cur==NULL)return vector<int>{0,0};
vector<int> left=robTree(cur->left);
vector<int> right=robTree(cur->right);
int val1=max(left[0],left[1])+max(right[0],right[1]);//不偷cur点,那么可以偷也可以不偷左右节点
int val2=cur->val+left[0]+right[0];//偷cur点
return {val1,val2};
}
};
买卖股票的最佳时机
dp[i][0] 表示第i天持有股票所得最多现金
dp[i][1] 表示第i天不持有股票所得最多现金
买卖股票的最佳时机III和IV
dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j]
- 表示不操作
- 第一次持有股票
- 第一次不持有股票
- 第二次持有股票
- 第二次不持有股票
最佳买卖股票时机含冷冻期
dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。
- 状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
- 不持有股票状态,这里就有两种卖出股票状态
- 状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
- 状态三:今天卖出股票
- 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!
买卖股票的最佳时机II
dp[i][0] 表示第i天不持有股票所得最多现金
dp[i][1] 表示第i天持有股票所得最多现金
最长连续递增序列
dp[i]:以下标i为结尾的连续递增的子序列长度为dp[i]
单调栈主要学了栈的另一种运用
致此一刷完结,要进入基础四大件的学习啦,当然还有二刷随想录
有点小问题是,课题还没有开始做头痛hhh