337.打家劫舍(树形DP动态规划)
class Solution {
public:
int rob(TreeNode* root) {
// 此时数的节点有两个状态 0 不偷 // 1 偷
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
vector<int> robTree(TreeNode* cur) {
// 递归终止条件
if (cur == nullptr) {
return vector<int>{0, 0};
}
// 后序遍历改二叉树 因为最终我们要去操作这个二叉树 计算树节点的最大值
// 此时的left 和 right就是dp[]数组
vector<int> left(2);
left = robTree(cur->left);
vector<int> right(2);
right = robTree(cur->right);
// 偷当前根节点 则他的子节点就不能偷
int result1 = cur->val + left[0] + right[0];
// 不偷根节点 那么可以偷也可以不偷左右节点
// //偷的话要偷左右子节点中最大的情况
int result2 = max(left[0], left[1]) + max(right[0], right[1]);
// 注意:这里是按数组的顺序返回,不偷cur对应result2对应result[0];
return {result2, result1};
}
};
1.dp[j]的定义 本题中dp[j]被定义为 left(2),right(2)
此时left与right暂存每次递归回来的
left[0]:不偷当前左子树的跟节点 left[1]:偷当前左子树的跟节点
right[0];不偷当前右子树的根节点 right[1];偷当前右子树的根节点
// 此时的left 和 right就是dp[]数组
vector<int> left(2);
left = robTree(cur->left);
vector<int> right(2);
right = robTree(cur->right);
注意:每次递归返回的result2(不偷) 和 result1(偷)是与 left[0],left[1] //right[0],right[1] 一一对应的,
所以切记在返回递归值的时候不能返回错顺序
***不能返回return{result1,result2}; 会影响后序的递归
// 注意:这里是按数组的顺序返回,不偷cur对应result2对应result[0];
return {result2, result1};
}
2.然后在每次递归的时候,都需要上一次递归出来的left[0],[1]//right[0],[1] 进动态转移方程内进行递推
// 偷当前根节点 则他的子节点就不能偷
int result1 = cur->val + left[0] + right[0];
// 不偷根节点 那么可以偷也可以不偷左右节点
// //偷的话要偷左右子节点中最大的情况
int result2 = max(left[0], left[1]) + max(right[0], right[1]);
3.最终返回全部递归结束后所得到的不偷当前根节点和偷当前跟节点的最大值
返回一个存储着递归结束值的数组(偷cur节点的最大值,不偷cur结点的最大值)
// 注意:这里是按数组的顺序返回,不偷cur对应result2对应result[0];
return {result2, result1};
4.最终经过rob函数根据所偷的最大值来确定是否要选则偷取cur节点
int rob(TreeNode* root) {
// 此时数的节点有两个状态 0 不偷 // 1 偷
vector<int> result = robTree(root);
return max(result[0], result[1]);
}
121.买卖股票最佳时机(动态规划)
121.买卖股票最佳时机(一共只能买一股)
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size() + 1, vector<int>(2, 0));
// 0是持有 1是不持有
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.size(); i++) {
// 保持不卖 买入股票
dp[i][0]=max(dp[i-1][0],-prices[i]);
// 保持不买 卖出股票
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.size()-1][1];
}
};
1.股票的状态有两个
所以可知dp为二维数组表示当前股票的状态
(1) dp[1]手中没有股票
(2) dp[0]手中有股票
(3)dp[i][0]
表示到第i
天为止, 如果你持有股票,你能获得的最大利润。
(4)dp[i][1]
表示到第i
天为止, 如果你不持有股票,你能获得的最大利润。
(5)dp[i-1][0]
前一天就持有股票的利润
(6)dp[i-1][1]
前一天不持有股票的利润
vector<vector<int>> dp(prices.size() + 1, vector<int>(2, 0));
// 0是持有 1是不持有
2.dp数组的初始化
初始资金为0;
// 0是持有 1是不持有
dp[0][0] = -prices[0]; 表示手中持有股票 买入了股票 就要用初始资金减去股票的价格-princes[i];
dp[0][1] = 0; 表示手中没有股票的状态
// 0是持有 1是不持有
dp[0][0] = -prices[0];
dp[0][1] = 0;
3.递推公式
for (int i = 1; i < prices.size(); i++) {
// 保持不卖 买入股票
dp[i][0]=max(dp[i-1][0],-prices[i]);
// 保持不买 卖出股票
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
// 保持不卖 买入股票
dp[i][0]=max(dp[i-1][0],-prices[i]);
表示从 保持不卖出股票 和 买入股票 中选出最大的值
// 保持不买 卖出股票
dp[i][1]=max(dp[i-1][1],dp[i-1][0]+prices[i]);
表示从 保持不买 和 卖出股票 中 选出最大的值
4.最终返回手中不持有股票时的最大值
return dp[prices.size()-1][1];
122.买卖股票最佳时机II(动态规划)
122.买卖股票的最佳时机II(每天最多持有一只股票)
给你一个整数数组 prices
,其中 prices[i]
表示某支股票第 i
天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>> dp(prices.size() + 1, vector<int>(2));
// 持有股票
dp[0][0] = -prices[0];
// 不持有股票
dp[0][1] = 0;
for (int i=1; i < prices.size(); i++) {
//卖出股票的利润-买的股票的消费
dp[i][0] = max(dp[i - 1][0], dp[i-1][1] - prices[i]);
//持有股票的价值+卖出股票的利润
dp[i][1] = max(dp[i - 1][1], dp[i-1][0] + prices[i]);
}
//出最后一天持有或不持有股票时的最大利润
return max(dp[prices.size()-1][1],dp[prices.size()-1][0]);
}
};
1.股票的状态有两个
(1) dp[1]手中没有股票
(2) dp[0]手中有股票
(3)dp[i][0]
表示到第i
天为止, 如果你持有股票,你能获得的最大利润。
(4)dp[i][1]
表示到第i
天为止, 如果你不持有股票,你能获得的最大利润。
(5)dp[i-1][0]
前一天就持有股票的利润
(6)dp[i-1][1]
前一天不持有股票的利润
所以可知dp为二维数组表示当前股票的状态
// 0是持有 1是不持有
dp[0][0] = -prices[0];
dp[0][1] = 0;
2.dp数组的初始化
初始资金为0;
// 0是持有 1是不持有
dp[0][0] = -prices[0]; 表示手中持有股票 买入了股票 就要用初始资金减去股票的价格-princes[i];
dp[0][1] = 0; 表示手中没有股票的状态
3.递推公式
与上题不同的是
//卖出股票的利润-买的股票的消费
dp[i][0] = max(dp[i - 1][0], dp[i-1][1] - prices[i]);
是在第i
天买入股票的利润dp[i-1][1] - prices[i]
。
因为你只能持有一股,所以如果你在第i
天买入,那么你在前一天肯定是不持有股票的,所以要从dp[i-1][1]
中减去prices[i]
。是在第i
天买入股票的利润dp[i-1][1] - prices[i]
for (int i=1; i < prices.size(); i++) {
//卖出股票的利润-买的股票的消费
dp[i][0] = max(dp[i - 1][0], dp[i-1][1] - prices[i]);
//持有股票的价值+卖出股票的利润
dp[i][1] = max(dp[i - 1][1], dp[i-1][0] + prices[i]);
}
4.最终返回手中 不持有股票 或 持有股票时 的最大值
1.与上题不同的是max(dp[prices.size()-1][1],dp[prices.size()-1][0])
是为了找出最后一天 持有 或 不持有 股票时的最大利润。
//max(dp[prices.size()-1][1],dp[prices.size()-1][0])是为了找出最后一天持有或不持有股票时的最大利润。
return max(dp[prices.size()-1][1],dp[prices.size()-1][0]);
300.最长递增子序列(动态规划)
给你一个整数数组 nums
,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组 [0,3,1,6,2,2,7]
的子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if (nums.size() <= 1) return nums.size();
//dp的含义是以nums[i]结尾的子序列的最大长度
vector<int> dp(nums.size()+1,1);
dp[0]=1;
int result=0;
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
//从0到i之间 字符串递增的最大长度
dp[i]=max(dp[j]+1,dp[i]);
}
}
//从以nums[0]-nums[i]为结尾的子序列中找到最长递增子序列
result = max(result, dp[i]);
}
return result;
}
};
1.dp[i]的含义
(1)dp[i]的含义是以nums[i]为结尾的子序列的最大长度;
(2)dp[i]的初始化
将dp[i]的所有值全部初始化成1(最小子序列长度)方便后续递归
vector<int> dp(nums.size()+1,1);
dp[0]=1;
2.递归方程
代码中的双层循环是用来填充dp
数组的。外层循环遍历数组nums
中的每个元素,内层循环遍历当前元素之前的所有元素。
对于每个nums[i]
,我们检查所有在它之前的元素nums[j]
(j < i
)。
如果nums[i]
大于nums[j]
,这意味着我们可以将nums[i]
添加到以nums[j]
结尾的递增子序列的末尾,从而形成一个新的递增子序列。
因此,我们可以更新dp[i]
为dp[j] + 1
和当前dp[i]
的较大值。这表示以nums[i]
结尾的最长递增子序列可以是包含nums[i]
和以nums[j]
结尾的递增子序列。
for(int i=1;i<nums.size();i++){
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
//从0到i之间 字符串递增的最大长度
dp[i]=max(dp[j]+1,dp[i]);
}
}
3.整个数组中的子长子序列result
result
将包含整个数组nums
中的最长递增子序列的长度,我们将其返回作为函数的输出
//从以nums[0]-nums[i]为结尾的子序列中找到最长递增子序列
result = max(result, dp[i]);
647.最长 连续 递增子序列(动态规划)
给定一个未经排序的整数数组,找到最长且 连续递增的子序列 ,并返回该序列的长度。
连续递增的子序列 可以由两个下标 l
和 r
(l < r
)确定,如果对于每个 l <= i < r
,都有 nums[i] < nums[i + 1]
,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]]
就是连续递增子序列。
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
if(nums.size()<=1)return nums.size();
//一定要明确dp数组的含义是以nums[i]为结尾的最大连续子序列长度
vector<int> dp(nums.size()+1,1);
dp[0]=1;
int result=0;
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]){
dp[i]=max(dp[i-1]+1,dp[i]);
}
//最后必须需要这一步 来获取0-nums[i]的最大连续子序列长度
result=max(result,dp[i]);
}
return result;
}
};
1.递归方程
dp[i]
表示以nums[i]
结尾的最长连续递增子序列的长度。
dp[i] = max(dp[i-1] + 1, dp[i]);
这行代码的目的是为了在遍历数组nums
时更新dp
数组。这里的关键是理解dp[i-1] + 1
和dp[i]
的含义:
-
dp[i-1] + 1
:这表示如果nums[i]
比nums[i-1]
大,那么nums[i]
可以加入到以nums[i-1]
结尾的递增子序列中,形成一个新的递增子序列。因此,以nums[i]
结尾的最长连续递增子序列的长度就是以nums[i-1]
结尾的最长连续递增子序列的长度加一。 -
dp[i]
:这是dp
数组的当前值,它已经初始化为1,因为每个元素本身就是一个长度为1的递增子序列。
3.max(dp[i-1] + 1, dp[i])
取的是这两个值中的较大值。
如果nums[i]
大于nums[i-1]
,那么dp[i]
就会被更新为dp[i-1] + 1
,表示找到了一个更长的递增子序列。
如果nums[i]
不大于nums[i-1]
,那么dp[i]
保持为1,因为nums[i]
只能构成一个长度为1的递增子序列。
最后,result
用来记录遍历过程中遇到的最长连续递增子序列的长度,通过比较result
和dp[i]
的值来更新result
。
for(int i=1;i<nums.size();i++){
if(nums[i]>nums[i-1]){
dp[i]=max(dp[i-1]+1,dp[i]);
}
//最后必须需要这一步 来获取0-nums[i]的最大连续子序列长度
result=max(result,dp[i]);
}
return result;
}