这是一套经典的动态规划题目,题目主干都是给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格,在不同的情况下求在什么时候买卖以获取最大利润。
- 121题是只能买卖一次的情况下求最大利润;
- 122题可以买卖任意多次;
- 188题是前面三题的泛化版本,给定参数k,求买卖k次情况下的最大利润;
- 309题是在122题买卖任意多次的基础上加上冷冻期即T+1才能卖出。
其中买卖k次的188题是标准的三维动态规划题,用解这题的通用方可以稍加改造就能解决前三题,当然前三题也有单独的特定简单解法,这里为了练习还是重点看动态规划的解决方法。
在188题的基础上可以再变形出更多题目,比如现在这些题都是最多只能持有1股,如果可以持有n股(每天只能买入或卖出1股),也能用这个188题的三维DP通解来解决。
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
int res = 0;
int min_price = prices[0];
for (int i=0;i<n;i++)
{
res = max(res, prices[i] - min_price);
min_price = min(min_price, prices[i]);
}
return res;
}
};
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
vector<vector<int>> dp (n, vector<int> (2,0));
// 状态 dp[i][j] 表示:在索引为 i 的这一天,用户手上状态为 j 所获得的最大利润。
// 0:用户手上不持股所能获得的最大利润,特指卖出股票以后的不持股,非指没有进行过任何交易的不持股
// 1:用户手上持股所能获得的最大利润
// 注意:因为题目限制只能交易一次,因此状态只可能从 1 到 0,不可能从 0 到 1
dp[0][0] = 0;
dp[0][1] = -prices[0];
// 状态转移方程:
// dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i]) 今天不持有股票,可以昨天不持有rest下来,也可以是昨天持有,今天卖了股票
// dp[i][1] = max(dp[i - 1][1], - prices[i]) 今天因为持有股票,可以是昨天就持有,今天刚买的,因为只进行交易一次,所以只会减一个
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], - prices[i]);
}
return dp[n-1][0];
}
};
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
###标准二维DP动态规划,第i天只有两种状态,不持有或持有股票,当天不持有股票的状态可能来自昨天卖出或者昨天也不持有,同理,当天持有股票的状态可能来自昨天买入或者昨天也持有中,取最后一天的不持有股票状态就是问题的解。(没有交易次数限制)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
vector<vector<int>> dp (n, vector<int> (2,0));
dp[0][0] = 0; // 不持有股票,持有现金
dp[0][1] = -prices[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][0];
}
};
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
int dp_i_0= 0;
int dp_i_1 = -prices[0];
for (int i=1;i<n;i++)
{
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, dp_i_0 - prices[i]);
}
return dp_i_0;
}
};
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例1:
输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
标准的三维DP动态规划,三个维度,第一维表示天,第二维表示交易了几次,第三维表示是否持有股票。与下面188题买卖股票4一样的代码,把交易k次定义为2次。当然也可以把内层的for循环拆出来,分别列出交易0次、1次、2次的状态转移方程即可
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n =prices.size();
if (n<=1) return 0;
int k = 2;
vector<vector<vector<int>>> dp (n, vector<vector<int>> (k+1, vector<int>(2, 0)));
// 初始状态
for (int j =0; j<k+1;j++)
{
dp[0][j][0] = 0; // sell 不持有股票
dp[0][j][1] = -prices[0]; // buy 持有股票
}
for (int i=1;i<n;i++)
{
for (int j=k;j>=1;j--)
// 从大到小进行遍历,因为计算当前K的值,只依赖K-1和i-1情况下的值,
// 从大到小可以省去一个维度的数组,更新上一次计算的结果即可
{
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1]+ prices[i]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
}
}
return dp[n-1][k][0];
}
};
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例1:
输入: [2,4,1], k = 2
输出: 2
解释: 在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例2:
输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
if (k>n/2) // 相当于没有交易次数限制
{
int res = 0;
for (int i=1;i<n;i++)
{
if (prices[i] > prices[i-1])
{
res += prices[i] - prices[i-1];
}
}
return res;
}
vector<vector<vector<int>>> dp (n, vector<vector<int>> (k+1, vector<int>(2,0)));
for (int j=0;j<k+1;j++)
{
dp[0][j][0] = 0;
dp[0][j][1] = -prices[0];
}
for (int i=1;i<n;i++)
{
for(int j=k;j>=1;j--)
{
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]); // buy的时候算交易一次,
}
}
return dp[n-1][k][0];
}
};
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
if (n<=1) return 0;
if (k>n/2)
{
int dp_i_0= 0;
int dp_i_1 = -prices[0];
for (int i=1;i<n;i++)
{
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, dp_i_0 - prices[i]);
}
return dp_i_0;
}
vector<vector<vector<int>>> dp(n, vector<vector<int>> (k+1, vector<int>(2,0)));
for (int j=0;j<k+1;j++)
{
dp[0][j][0] = 0;
dp[0][j][1] = -prices[0];
}
for (int i=1;i<n;i++)
{
for (int j=k;j>=1;j--)
{
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i]);
dp[i][j][1] = max(dp[i-1][j][1], dp[i-1][j-1][0] - prices[i]);
}
}
return dp[n-1][k][0];
}
};
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
- 不持股可以由这两个状态转换而来:(1)昨天不持股,今天什么都不操作,仍然不持股。(2)昨天持股,今天卖了一股。
- 持股可以由这两个状态转换而来:(1)昨天持股,今天什么都不操作,仍然持股;(2)昨天处在冷冻期,今天买了一股;
- 处在冷冻期只可以由不持股转换而来,因为题目中说,刚刚把股票卖了,需要冷冻 1 天。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n<=1) return 0;
vector<vector<int>> dp (n, vector<int>(3,0));
dp[0][0] = 0; // 不持有股票
dp[0][1] = -prices[0]; // 持有股票
dp[0][2] = 0; // 处于冷冻期
for (int i=1;i<n;i++)
{
//第i天不持有股票的情况有两种
//a.第i-1天也不持有股票
//b.第i-1天持股给卖了
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]);
//第i天持有股票有两种情况
//a.第i-1天也持有股票,第i天不操作,
//b.第i-1天处于冷冻期,在第i天买入
dp[i][1] = max(dp[i-1][1], dp[i-1][2]-prices[i]);
//第i天是冷冻期只有一种情况:处在冷冻期只可以由不持股转换而来
dp[i][2] = dp[i-1][0];
}
//最后最大利润为最后一天,不持有股票或者进入冷冻期的情况
return max(dp[n-1][0], dp[n-1][2]);
}
};
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
if(n<=1) return 0;
int dp_i_0 = 0;
int dp_i_1 = -prices[0];
int dp_i_2 = 0;
for (int i=1;i<n;i++)
{
int temp =dp_i_0;
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, dp_i_2 - prices[i]);
dp_i_2 = temp;
}
return max(dp_i_0,dp_i_2);
}
};
给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每次交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
示例:
输入: prices = [1, 3, 2, 8, 4, 9], fee = 2
输出: 8
解释: 能够达到的最大利润:
在此处买入 prices[0] = 1
在此处卖出 prices[3] = 8
在此处买入 prices[4] = 4
在此处卖出 prices[5] = 9
总利润: ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
if (n<=1) return 0;
vector<vector<int>> dp(n,vector<int>(2,0));
dp[0][0] = 0;
dp[0][1] = -prices[0]-fee;
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] - fee);
}
return dp[n-1][0];
}
};
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
if (n<=1) return 0;
int dp_i_0 = 0;
int dp_i_1 = -prices[0]-fee;
for (int i=1;i<n;i++)
{
dp_i_0 = max(dp_i_0, dp_i_1 + prices[i]);
dp_i_1 = max(dp_i_1, dp_i_0 - prices[i] -fee);
}
return dp_i_0;
}
};