文章目录
- 198. 打家劫舍
- 213. 打家劫舍 II
- 337. 打家劫舍 III
- 121. 买卖股票的最佳时机
- 动态规划
- 贪心算法
- 122. 买卖股票的最佳时机 II
- 动态规划
- 贪心算法
- 123.买卖股票的最佳时机III
- 188.买卖股票的最佳时机IV
- 309.最佳买卖股票时机含冷冻期
- 714.买卖股票的最佳时机含手续费
198. 打家劫舍
步骤
-
确定dp数组以及下标的含义
dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i] -
确定递推公式
- 决定dp[i]的因素就是第i房间偷还是不偷,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
- 偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,第i-1房不能偷,找出下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为 dp[i-2] +第i房间偷到的钱。
- 不偷第i房间,那么dp[i] = dp[i - 1],考虑i-1房,不一定要偷i-1房,最多可以偷窃的金额不变,仍为dp[i-1]
- dp[i]取最大值,
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
-
dp数组如何初始化
递推公式的基础依赖于dp[0] 和 dp[1]
i=0时,dp[0]只能是nums[0],i=1时,dp[1]取nums[0]和nums[1]的最大值
dp[0]= =nums[0],dp[1]=max(nums[0], nums[1]); -
确定遍历顺序
dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历 -
举例推导dp数组
输入: [2,7,9,3,1]
-
C++实现
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(), 0);
dp[0] = nums[0];
dp[1] = max(nums[0], nums[1]);
for(int i=2; i<nums.size(); i++)
{
dp[i] = max(dp[i-1], dp[i-2]+nums[i]);
}
return dp[nums.size()-1];
}
};
213. 打家劫舍 II
和198.打家劫舍类似,但是房屋是闭环的,分三种情况
情况三考虑包含尾元素,但不一定要选尾部元素,并且对于情况三,取nums[1] 和 nums[3]就是最大的。
同时情况二和情况三都包含了情况一,所以只考虑情况二和情况三就可以了。
步骤
-
确定dp数组以及下标的含义
dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i] -
确定递推公式
- 决定dp[i]的因素就是第i房间偷还是不偷,当前房屋偷与不偷取决于 前一个房屋和前两个房屋是否被偷了
- 偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,第i-1房不能偷,找出下标i-2(包括i-2)以内的房屋,最多可以偷窃的金额为 dp[i-2] +第i房间偷到的钱。
- 不偷第i房间,那么dp[i] = dp[i - 1],考虑i-1房,不一定要偷i-1房,最多可以偷窃的金额不变,仍为dp[i-1]
- dp[i]取最大值,
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
-
dp数组如何初始化
递推公式的基础依赖于dp[0] 和 dp[1]
i=0时,dp[0]只能是nums[0],i=1时,dp[1]取nums[0]和nums[1]的最大值
dp[0]= =nums[0],dp[1]=max(nums[0], nums[1]); -
确定遍历顺序
dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么从前到后遍历 -
C++实现
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0) return 0;
if(nums.size() == 1) return nums[0];
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-1], dp[i-2]+nums[i]);
}
return dp[end];
}
};
337. 打家劫舍 III
和198.打家劫舍类似,但是房屋是二叉树形结构,对于树的遍历方式,前中后序(深度优先搜索),层序遍历(广度优先搜索)。本题一定是后序遍历,因为通过递归函数的返回值来做下一步计算。
与198.打家劫舍,213.打家劫舍II一样,关键在于当前节点抢还是不抢。如果抢了当前节点,两个孩子就不能动,如果没抢当前节点,就可以考虑抢左右孩子
动态规划使用状态转移容器来记录状态的变化,可以使用一个长度为2的数组,记录当前节点偷与不偷所得到的的最大金钱。
步骤
- 确定递归函数的参数和返回值
- 要求一个节点 偷与不偷的两个状态所得到的金钱,那么返回值就是一个长度为2的数组也就是dp数组。
- dp数组以及下标的含义:dp数组是一个长度为2的数组,下标为0记录不偷该节点所得到的的最大金钱,下标为1记录偷该节点所得到的的最大金钱。
-
确定终止条件
遍历的过程中,如果遇到空节点的话,无论偷还是不偷都是0,返回,相当于dp数组的初始化 -
确定遍历顺序
- 使用后序遍历,要通过递归函数的返回值来做下一步计算。
- 通过递归左节点,得到左节点偷与不偷的金钱。
- 通过递归右节点,得到右节点偷与不偷的金钱。
- 确定单层递归的逻辑
- 偷当前节点,左右孩子不能偷,
val1 = cur->val + left[0] + right[0];
- 不偷当前节点,左右孩子可以偷,选最大的,
val2 = max(left[0], left[1]) + max(right[0], right[1]);
- 当前节点的状态为
{val2, val1};
即 {不偷当前节点得到的最大金钱,偷当前节点得到的最大金钱}
-
举例推导dp数组
示例1
-
C++实现
/**
* 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 == nullptr) return vector<int>{0, 0};
vector<int> left = robTree(cur->left);
vector<int> right = robTree(cur->right);
//偷cur 不偷左右节点
int var1 = cur->val + left[0] + right[0];
//不偷cur 可以偷或者不偷左右节点 取较大值
int var2 = max(left[0], left[1]) + max(right[0], right[1]);
return {var2, var1};
}
};
121. 买卖股票的最佳时机
动态规划
步骤
- 确定dp数组以及下标的含义
- dp[i][0],表示第i天持有股票所得最多现金
- dp[i][1],表示第i天不持有股票所得最多现金
- “持有”不代表就是当天“买入”,也有可能是昨天就买入了,今天保持持有的状态
- 确定递推公式
-
dp[i][0]可以由两个状态推出来
- 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
- 第i天买入股票,所得现金=买入今天的股票后所得现金即,-prices[i]
- dp[i][0]取所得现金最大的,
dp[i][0] = max(dp[i - 1][0], -prices[i]);
-
dp[i][1]可以由两个状态推出来
- 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
- 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,prices[i] + dp[i - 1][0]
- dp[i][1]取所得现金最大的,
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
- dp数组如何初始化
- dp[0][0]表示第0天持有股票,买入了股票,
dp[0][0] = -prices[0];
- dp[0][1]表示第0天不持有股票,没有买股票,现金为0,
dp[0][1] = 0;
-
确定遍历顺序
dp[i]由dp[i - 1]推导出而来,从前向后遍历 -
举例推导dp数组
输入: 输入:[7,1,5,3,6,4]
最终结果是dp[5][1],而不是dp[5][0],因为不持有股票状态所得金钱一定比持有股票状态得到的多 -
C++实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
//二维dp数组
int len = prices.size();
if(len == 0) return 0;
vector<vector<int>> dp(len, vector<int>(2));
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i=1; i<len; i++)
{
dp[i][0] = max(dp[i-1][0], -prices[i]);
dp[i][1] = max(dp[i-1][1], prices[i]+dp[i-1][0]);
}
return dp[len - 1][1];
/*
//二维dp滚轮数组
int len = prices.size();
if(len == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for(int i=1; i<len; i++)
{
dp[i % 2][0] = max(dp[(i-1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1)%2][0]);
}
return dp[(len-1) % 2][1];*/
}
};
贪心算法
class Solution {
public:
int maxProfit(vector<int>& prices) {
//贪心算法
int low = INT_MAX;
int result = 0;
for(int i=0; i<prices.size(); i++)
{
low = min(low, prices[i]);// 取最左最小价格
result = max(result, prices[i] - low);// 直接取最大区间利润
}
return result;
}
};
122. 买卖股票的最佳时机 II
动态规划
本题和121. 买卖股票的最佳时机的唯一区别是股票可以买卖多次,注意最多只能持有 一只股票,再次购买前要出售掉之前的股票。区别主要是体现在递推公式上,其他都一样。
步骤
- 确定dp数组以及下标的含义
- dp[i][0],表示第i天持有股票所得最多现金
- dp[i][1],表示第i天不持有股票所得最多现金
- “持有”不代表就是当天“买入”,也有可能是昨天就买入了,今天保持持有的状态
- 确定递推公式
-
dp[i][0]可以由两个状态推出来
- 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
- 第i天买入股票,所得现金=昨天不持有股票的所得现金减去今天的股票价格,dp[i - 1][1] - prices[i],区别点
- dp[i][0]取所得现金最大的,
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
-
dp[i][1]可以由两个状态推出来
- 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
- 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,dp[i - 1][0] + prices[i]
- dp[i][1]取所得现金最大的,
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
- dp数组如何初始化
- dp[0][0]表示第0天持有股票,
dp[0][0] -= prices[0];
- dp[0][1]表示第0天不持有股票,没有买股票,现金为0,
dp[0][1] = 0;
-
确定遍历顺序
dp[i]由dp[i - 1]推导出而来,从前向后遍历 -
C++实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
//动态规划
int n = prices.size();
vector<vector<int>> dp(n, vector<int>(2, 0));
dp[0][0] = -prices[0];//持股票
dp[0][1] = 0;//持现金
for(int i=1; i<n; i++)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]);//第i天买了股票
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]);//第i天卖了股票
}
return dp[n-1][1];
/*
//二维dp滚轮数组
int len = prices.size();
if(len == 0) return 0;
vector<vector<int>> dp(2, vector<int>(2));
dp[0][0] -= prices[0];
dp[0][1] = 0;
for(int i=1; i<len; i++)
{
dp[i % 2][0] = max(dp[(i-1) % 2][0], -prices[i]);
dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1)%2][0]);
}
return dp[(len-1) % 2][1];*/
}
};
贪心算法
class Solution {
public:
//贪心算法
int maxProfit(vector<int>& prices) {
int result = 0;
for(int i=1; i<prices.size(); i++)//i从1开始,第二天才有利润
{
//累加每天的正利润,最后求的最大利润
result += max(prices[i]-prices[i-1], 0);
}
return result;
}
};
123.买卖股票的最佳时机III
和122.买卖股票的最佳时机II主要区别在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。因此,可以假设一天有五种状态来记录:
0-没有操作
1-第一次持有股票
2-第一次不持有股票
3-第二次持有股票
4-第二次不持有股票
步骤
-
确定dp数组以及下标的含义
dp[i][j]:i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。
要注意的是,dp[i][1]表示的是第i天持有股票的状态,并不一定是第i天买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续持有股票的这个状态。 -
确定递推公式
五种状态的推导
-
dp[i][1],持有股票,两个状态中取最大值
- 第i天买入股票,那么dp[i][1] = dp[i-1][0] - prices[i]
- 第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
- dp[i][0]取所得现金最大的,
dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
-
dp[i][2],不持有股票,两个状态中取最大值
- 第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
- 第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
- dp[i][2]取所得现金最大的,
dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
-
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
-
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
- dp数组如何初始化
- 第0天没有操作,
dp[0][0] = 0;
- 第0天第一次买入的操作,
dp[0][1] = -prices[0];
- 第0天第一次卖出的操作,当天买入,当天卖出,
dp[0][2] = 0;
- 第0天第二次买入操作,第二次买入依赖于第一次卖出的状态,相当于第0天买入-卖出-再买入,
dp[0][3] = -prices[0];
- 第0天第二次买入操作,第0天买入-卖出-再买入-再卖出,
dp[0][4] = 0;
-
确定遍历顺序
dp[i]依赖dp[i - 1],从前向后遍历 -
举例推导dp数组
输入[1,2,3,4,5]
红色框为最后两次卖出的状态,现金最多的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出,因为dp[i][j]表示第i天状态j所剩最大现金。
- C++实现
保存每一天的五种状态
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0) return 0;
//保存每一天的五种状态
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));//第0天没有操作 第一次卖出 第二次卖出操作
dp[0][1] = -prices[0];//第0天第一次买股票
dp[0][3] = -prices[0];//第0天第二次买股票
for(int i = 1; i<prices.size(); i++)
{
dp[i][0] = dp[i-1][0];
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]);//第一次持有股票
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i]);//第一次不持有股票
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i]);
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]);
}
return dp[prices.size()-1][4];//第二次不持有股票的时候所得现金最多
}
};
滚动数组-优化空间-只保存当天的五种状态
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0) return 0;
//保存当天的五种状态
vector<int> dp(5, 0);
dp[1] = -prices[0];
dp[3] = -prices[0];
for(int i=1; i<prices.size(); i++)
{
dp[1] = max(dp[1], dp[0] - prices[i]);//max()里的dp都是前一天的状态
dp[2] = max(dp[2], dp[1] + prices[i]);
dp[3] = max(dp[3], dp[2] - prices[i]);
dp[4] = max(dp[4], dp[3] + prices[i]);
}
return dp[4];//第二次不持有股票的时候所得现金最多
}
};
188.买卖股票的最佳时机IV
在 123.买卖股票的最佳时机III的基础上,要求至多有k次交易,那么当天有2k+1次操作:
0-不操作(可以不定义)
1-第一次持有股票
2-第一次不持有股票
3-第二次持有股票
4-第二次不持有股票
5-第三次持有股票
…
除了0以外,偶数就是卖出,奇数就是买入
步骤
-
确定dp数组以及下标的含义
dp[i][j]:i表示第i天,j有2k+1种状态,dp[i][j]表示第i天状态j所剩最大现金。
要注意的是,dp[i][1]表示的是第i天持有股票的状态,并不一定是第i天买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续持有股票的这个状态。 -
确定递推公式
-
dp[i][1],持有股票,两个状态中取最大值
- 第i天买入股票,dp[i][1] = dp[i-1][0] - prices[i]
- 第i天没有操作,沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]
- dp[i][0]取所得现金最大的,
dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);
-
dp[i][2],不持有股票,两个状态中取最大值
- 第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
- 第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]
- dp[i][2]取所得现金最大的,
dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2]);
和123.买卖股票的最佳时机III 最大的区别就是j为奇数是买入,偶数是卖出状态
- dp数组如何初始化
- 第0天没有操作,
dp[0][0] = 0;
- 第0天第一次买入的操作,
dp[0][1] = -prices[0];
- 第0天第一次卖出的操作,当天买入,当天卖出,
dp[0][2] = 0;
- 第0天第二次买入操作,第二次买入依赖于第一次卖出的状态,相当于第0天买入-卖出-再买入,
dp[0][3] = -prices[0];
- 第0天第二次买入操作,第0天买入-卖出-再买入-再卖出,
dp[0][4] = 0;
dp[0][j]当j为奇数时都初始化为 -prices[0],j为偶数是卖、奇数是买的状态
-
确定遍历顺序
dp[i]依赖dp[i - 1],从前向后遍历 -
举例推导dp数组
输入[1,2,3,4,5],k=2
最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k],红框部分
- C++实现
注意怎么模拟j为偶数是卖、奇数是买的状态
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
if(prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2*k+1, 0));
//初始化dp[i][j] j为奇数的状态
for(int j=1; j<2 * k; j += 2)
{
dp[0][j] = -prices[0];
}
for(int i=1; i<prices.size(); i++)
{
for(int j=0; j< 2 * k - 1; j += 2)
{
dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j] - prices[i]);
dp[i][j+2] = max(dp[i-1][j+2], dp[i-1][j+1] + prices[i]);
}
}
return dp[prices.size()-1][2 * k];
}
};
309.最佳买卖股票时机含冷冻期
步骤
- 确定dp数组以及下标的含义
dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。
在122.买卖股票的最佳时机II的基础上加了一个冷冻期,相比于122题有两个状态,持有股票和不持有股票,而本题有四个状态:
- j=0,状态一:持有股票状态,今天买入股票,或者之前买入了股票然后没有操作,一直持有
- j=1,状态二:不持有股票状态,保持卖出股票的状态,比如两天前卖出了股票,度过一天冷冻期,或者前一天卖出股票状态,一直没操作
- j=2,状态三:不持有股票状态,今天卖出股票
- j=3,状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天
要注意的是,冷冻期的前一天,只能是状态三—今天卖出股票状态,如果是状态二—不持有股票状态,就不一定是卖出股票的操作了。
- 确定递推公式
-
dp[i][0],状态一,持有股票状态的两种方法
1)前一天持有股票,dp[i][0] = dp[i - 1][0]
2)前一天是冷冻期,dp[i - 1][3] - prices[i];或者前一天是保持卖出状态,dp[i - 1][1] - prices[i]
dp[i][0]取所得现金最大的,dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);
-
dp[i][1],状态二,保持卖出股票状态的两种方法
1)前一天是保持卖出状态,dp[i - 1][1]
2)前一天是冷冻期,dp[i - 1][3]
dp[i][1]取所得现金最大的,dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
-
dp[i][2],状态三,今天卖出股票状态
前一天只能是持有股票,今天卖出,dp[i][2] = dp[i - 1][0] + prices[i];
-
dp[i][3],状态四,达到冷冻期状态
前一天只能是卖出股票状态,dp[i][3] = dp[i - 1][2];
- dp数组如何初始化——第0天如何初始化
- 状态一,持有股票的话一定是当天买入股票,dp[0][0] = -prices[0]
- 状态二,保持卖出股票状态,如果i为1,第1天买入股票,那么dp[i - 1][1] - prices[i] = dp[0][1] - prices[1]。而dp[0][1],也就是第0天的状态二,只能初始为0。如果初始为其他数值,说明第1天买入股票后手里还剩有现金
- 状态三,今天卖出了股票,i=1,第1天买入股票再卖出股票,dp[i - 1][1] - prices[i] + prices[i] = dp[0][1] - prices[1] + prices[1]。那么dp[0][2]只能初始化为0。
- 状态四,同上分析,dp[0][3]也初始为0
-
确定遍历顺序
dp[i]由dp[i - 1]推导出而来,从前向后遍历 -
举例推导dp数组
输入[1,2,3,0,2]
最后结果是取状态二,状态三,和状态四的最大值。状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。 -
C++实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n == 0) return 0;
//0-状态一 持有股票
//1-状态二 保持卖出 之前的某一天卖出了股票
//2-状态三 今天卖出
//3-状态四 冷冻期
vector<vector<int>> dp(n, vector<int>(4, 0));//状态二三四 都初始化为0
dp[0][0] = -prices[0];//状态一 持有股票
for(int i=1; i<n; i++)
{
//前一天持有股票 或者前一天是冷冻期/前一天是保持卖出状态
dp[i][0] = max(dp[i-1][0], max(dp[i-1][3], dp[i-1][1]) - prices[i]);
//前一天是保持卖出 或者冷冻期
dp[i][1] = max(dp[i-1][1], dp[i-1][3]);
//前一天只能是持有股票
dp[i][2] = dp[i-1][0] + prices[i];
//前一天只能是卖出
dp[i][3] = dp[i-1][2];
}
return max(dp[n-1][1], max(dp[n-1][2], dp[n-1][3]));
}
};
714.买卖股票的最佳时机含手续费
相对于122.买卖股票的最佳时机II,本题只需要在计算卖出操作的时候减去手续费,其他一样,主要区别体现在递推公式
步骤
- 确定dp数组以及下标的含义
- dp[i][0],表示第i天持有股票所得最多现金,之前的某一天买入股票,不一定是今天买入
- dp[i][1],表示第i天不持有股票所得最多现金
- 确定递推公式
-
dp[i][0]可以由两个状态推出来
- 第i-1天就持有股票,所得现金=昨天持有股票的所得现金,dp[i - 1][0]
- 第i天买入股票,所得现金=昨天不持有股票的所得现金减去今天的股票价格,dp[i - 1][1] - prices[i]
- dp[i][0]取所得现金最大的,
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
-
dp[i][1]可以由两个状态推出来
- 第i-1天就不持有股票,所得现金=昨天不持有股票的所得现金,dp[i - 1][1]
- 第i天卖出股票,所得现金=按照今天股票价格卖出后所得现金,需要给手续费,dp[i - 1][0] + prices[i] - fee
- dp[i][1]取所得现金最大的,
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);
- dp数组如何初始化
- dp[0][0]表示第0天持有股票,买入了股票,
dp[0][0] = -prices[0];
- dp[0][1]表示第0天不持有股票,没有买股票,现金为0,
dp[0][1] = 0;
-
确定遍历顺序
dp[i]由dp[i - 1]推导出而来,从前向后遍历 -
C++实现
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int len = prices.size();
vector<vector<int>> dp(len, vector<int>(2, 0));
dp[0][0] = -prices[0];
//0-持有股票
//1-不持有股票
for(int i=1; i<len; 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] - fee);
}
return dp[len-1][1];
/*//贪心算法
int result = 0;
int minprice = prices[0];
for(int i=1; i<prices.size(); i++)
{
//低价买入 买入日期
if(prices[i] < minprice) minprice = prices[i];
//不买不卖 买不便宜 卖亏本
if(prices[i] >= minprice && prices[i] <= minprice+fee) continue;
//计算利润 最后一天计算利润才是真的卖出日期
if(prices[i] > minprice+fee)
{
result += prices[i] - minprice - fee;
minprice = prices[i] - fee;//每天要更新最低价格
}
}
return result;*/
}
};
劫舍系列分别是198题数组上连续元素二选一,213题成环之后连续元素二选一,377题在树上连续元素二选一,所能得到的最大价值。
股票系列分别是从买卖一次到买卖多次,从最多买卖两次到最多买卖k次,从冷冻期再到手续费:121题只能买卖一次,122题可以买卖多次,123题最多买卖两次,188题最多买卖k次,309题买卖多次且卖出一天有冷冻期,714题买卖多次且有手续费。