1.121买卖股票的最佳时机
1.问题解析
买卖一次获得的最大利润。
2.问题转换
price[i]-price[j]的最大值。
3.解题思路
- 每一天都有两种状态:持有和不持有两种状态。
- 对于持有状态分为两种情况:1.在今天之前已经买入。2.今天买入,前一天是不持有状态
- 对于不持有状态分为两种情况:1.再今天之前已经卖出。2.今天卖出,前一天是持有状态
- 最后取得的最大值一定是不持有状态。
4.为什么要使用动态规划?
(本题也可以使用贪心算法来实现,但是考虑到与后面题目的连续性,所以这里只考虑使用动态规划)因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。
5.动态规划的具体实现
- dp数组的下标及其含义:dp[i][0]:代表的是第i天持有。dp[i][1]代表的是第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]);//表示不持有状态下获得的最大收益
- 初始化:第一天如果持有的话,那么代表的就是今天买入,收益为-price[0]。如果不持有的话,代表今天不进行任何操作即收益还是为0.
- 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//第一种方法:使用差值的方法进行计算即可
/*
int n = prices.size();
int count = 0;
int maxs = 0;
for(int i = 0;i<n-1;i++){
count += prices[i+1] - prices[i];
if(count>maxs) maxs = count;
if(count<0) count = 0;
}
return maxs;*/
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], 0-prices[i]);
dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);
}
return dp[len - 1][1];
}
};
2.122买卖股票的最佳时机2
1.问题解析
买卖多次获得的最大利润。
2.问题转换
price[i]-price[j]为正值的累加和。
3.解题思路
- 每一天都有两种状态:持有和不持有两种状态。
- 对于持有状态分为两种情况:1.在今天之前已经买入。2.今天买入,前一天是不持有状态
- 对于不持有状态分为两种情况:1.再今天之前已经卖出。2.今天卖出,前一天是持有状态
- 最后取得的最大值一定是不持有状态。
4.为什么要使用动态规划?
(本题也可以使用贪心算法来实现,但是考虑到与后面题目的连续性,所以这里只考虑使用动态规划)因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。
5.动态规划的具体实现
- dp数组的下标及其含义:dp[i][0]:代表的是第i天持有。dp[i][1]代表的是第i天不持有
- 递推公式:dp[i][0] = max(dp[i-1][0],dp[i-1][1]-prices[i]);//表示持有状态下获得的最大收益(在这里由于可以多次买卖,所以如果是今天买入的话,前面的收益不再是0了,而是dp[i-1][1])。dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);//表示不持有状态下获得的最大收益。
- 初始化:第一天如果持有的话,那么代表的就是今天买入,收益为-price[0]。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
- 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//第一种方法:贪心算法
/*
int val = 0;
int count = 0;
int n = prices.size();
for(int i = 1;i<n;i++){
count = prices[i]-prices[i-1];
if(count>0){
val += count;
}
}
return val;*/
int n = prices.size();
vector<vector<int>> dp(n,vector<int>(2,0));
//dp[i][0]:代表第i天持有的最大利润
//dp[i][1]:代表第i天未持有的最大利润
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]);
dp[i][1] = max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[n-1][1];
}
};
3.123买卖股票的最佳时机
1.问题解析
买卖两次获得的最大利润。
2.问题转换
price[i]-price[j]的两个最大值。
3.解题思路
- 每一天都有五种状态:第一次持有、第二次持有、第一次不持有、第二次不持有和第三次不持有共五种状态。
- 对于第一次持有状态分为两种情况:1.在今天之前已经第一次买入了,今天不进行任何操作。2.今天第一次买入,前一天是第一次不持有状态。
- 对于第二次持有状态分为两种情况:1.在今天之前已经第二次买入了,今天不进行任何操作。2.今天第二次买入,前一天是第二次不持有状态。
- 对于第一次不持有状态:就是不进行任何操作。从来没有进行买入和卖出操作。
- 对于第二次不持有状态分为两种情况:1.在今天之前已经第一次的卖出了,今天不进行任何操作。2.今天是第一次卖出,前一天是第一次持有的状态。
- 对于第三次不持有状态分为两种情况:1.在今天之前已经第二次的卖出了,今天不进行任何操作。2.今天是第二次卖出,前一天是第二次持有的状态。
- 最后取得的最大值一定是第三次不持有状态。
4.为什么要使用动态规划?
因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。
5.动态规划的具体实现
- dp数组的下标及其含义:dp[i][0]:代表的是第i天第一次不持有。dp[i][1]代表的是第i天第一次持有。dp[i][2]:代表的是第i天第二次不持有。dp[i][3]代表的是第i天第二次持有。dp[i][4]:代表的是第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]); - 初始化:第一天第一次持有或者第二次,收益都是-price[0](因为第一天可以进行多次买入和卖出的操作,收益不变dp[0][3] = -prices[0]+prices[0]-prices[0]=-prices[0];)。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
- 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
int maxProfit(vector<int>& prices) {
//还有一种方法:就是使用一个vector存储正值,初始化的时候给一个0避免出现一种都是累加的求得的结果不够的情况
/*
int n = prices.size();
vector<int> result;
result.push_back(0);
int count = 0;
for(int i = 1;i<n;i++){
int diff = prices[i]-prices[i-1];
if(diff>=0){
count += diff;
}else{
result.push_back(count);
count = 0;
}
}
result.push_back(count);
sort(result.begin(),result.end());
int m = result.size();
int ret = result[m-1]+result[m-2];
return ret;
*/
int n = prices.size();
//dp[i][0]:无操作
//dp[i][1]:第一次持有
//dp[i][2]:第一次不持有
//dp[i][3]:第二次持有
//dp[i][4]:第二次不持有
vector<vector<int>> dp(n,vector<int>(5,0));
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for(int i = 1;i < n;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[n-1][4];
}
};
4.188买卖股票的最佳时机四
1.问题解析
买卖k次获得的最大利润。和上一题很类似,只是一个是具体的只有两次而这个是k次。
2.问题转换
price[i]-price[j]的k个最大值。
3.解题思路
- 每一天都有2k+1种状态:第一次持有...第k次持有、第一次不持有、第二次不持有...第k+1次不持有共五种状态。
- 对于第一次持有状态分为两种情况:1.在今天之前已经第一次买入了,今天不进行任何操作。2.今天第一次买入,前一天是第一次不持有状态。
- 对于第k次持有状态分为两种情况:1.在今天之前已经第k次买入了,今天不进行任何操作。2.今天第k次买入,前一天是第k次不持有状态。
- 对于第一次不持有状态:就是不进行任何操作。从来没有进行买入和卖出操作。
- 对于第二次不持有状态分为两种情况:1.在今天之前已经第一次的卖出了,今天不进行任何操作。2.今天是第一次卖出,前一天是第一次持有的状态。
- 对于第k+1次不持有状态分为两种情况:1.在今天之前已经第k次的卖出了,今天不进行任何操作。2.今天是第k次卖出,前一天是第k次持有的状态。
- 最后取得的最大值一定是第k+1次不持有状态。
4.为什么要使用动态规划?
因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。
5.动态规划的具体实现
- dp数组的下标及其含义:dp[i][0]:代表的是第i天第一次不持有。dp[i][1]代表的是第i天第一次持有。dp[i][2]:代表的是第i天第二次不持有。dp[i][3]代表的是第i天第二次持有。dp[i][4]:代表的是第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]);
} - 初始化:第一天第一次持有或者第k次,收益都是-price[0](因为第一天可以进行多次买入和卖出的操作,收益不变dp[0][k] = (k-1)*(-prices[0]+prices[0])-prices[0]=-prices[0];)。如果不持有的话,代表今天不进行任何操作或者买入又卖出,即收益还是为0.
- 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
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));
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];
}
};
5.309买卖股票的最佳时机含冷冻期
1.问题解析
有冷冻期情况下的price[i]-price[j]的累加的最大值
2.问题转换
有约束条件下的price[i]-price[j]的累加的最大值
3.解题思路
- 每一天都有四种状态:保持买入(可以买入),保持卖出(已经过了冷冻期),今天卖出,冷冻期共四种状态。
- 对于保持买入分为三种情况:前一天是保持卖出的状态今天买入,前一天是冷冻期今天买入和前一天已经是买入状态今天不进行任何操作。
- 对于保持买入分为两种情况:前一天是保持卖出的状态今天不进行任何操作和前一天是冷冻期今天还是不进行任何操作。
- 对于今天卖出状态:前一天必然是保持买入状态,今天卖出。
- 对于冷冻期状态:前一天必然是卖出状态。
- 收益最大的时候一定是卖出的状态(今天卖出、保持卖出、冷冻期)。
4.为什么要使用动态规划?
因为在这里每一天的状态一定是由前面一天的状态递推出来,所以考虑使用动态规划的方法。
5.动态规划的具体实现
- dp数组的下标及其含义:dp[i][0]:代表的是第i天保持买入状态。dp[i][1]代表的是第i天保持卖出的状态。dp[i][2]:代表的是第i天卖出。dp[i][3]代表的是第i天为冷冻期。
- 递推公式:dp[i][0] = max(dp[i-1][0],max(dp[i-1][3]-prices[i],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];//由来看前面的解题思路 - 初始化:第一天保持买入,收益都是-price[0]。默认其他情况下都是0(不持有状态,没有收益)。
- 遍历顺序:由前面可以的递推公式可以知道,采取的是从小到大的遍历方式。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
//四种状态
//状态0:保持买入的状态,三种情况,第一种是前一天就是买入状态,第二个是前一天是冷冻期,那么今天买入,第三个是保持卖出状态
//状态1:保持卖出的状态,两种情况,第一种是前一时刻为冷冻期,第二个是前一时刻就是保持卖出状态。
//状态2:今天卖出的状态,那么前一天是保持买入状态
//状态3:冷冻期,那么前一天就是卖出状态。
vector<vector<int>> dp(n,vector<int>(4,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]-prices[i],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]));//最后利润最大的时候一定是卖出的状态,但是卖出后的状态有三种
}
};