188. 买卖股票的最佳时机 IV
给你一个整数数组 prices
和一个整数 k
,其中 prices[i]
是某支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k
笔交易。也就是说,你最多可以买 k
次,卖 k
次。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:k = 2, prices = [2,4,1] 输出:2 解释:在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
思路
这就是昨天买卖股票III的升级版,从限定最多2次到最多k次,难点就在于,k
作为任意一个数值,如何像k=2
一样划分出确定的5
种状态?
这就需要分析状态编号的排布,找出状态编号的内部规律:
- 0 表示不操作
- 1 第一次买入
- 2 第一次卖出
- 3 第二次买入
- 4 第二次卖出
……
可以看出奇数为买入,偶数为卖出,且
j
的范围就是2*k+1
- dp数组及其下标含义:第
i
天的状态为j
,其最大资金为dp[i][j]
- 递推公式:同理,无论是买入卖出,每一天的状态都可以分为 “顺沿自前一天” 和 “当天操作” 两类,
i
(当天),i-1
(昨天),j
(当前状态),j-1
(上一状态)- 昨日顺沿:时间变化,状态不变,
dp[i-1][j]
- 当天操作:时间变化,状态变化,
dp[i-1][j-1]
- 昨日顺沿:时间变化,状态不变,
- 数组初始化:由于规定只能出售掉才能购买,在第0天的任意一次买入前都有n=[0,k-1]次完整的买卖操作,因此
dp[0][j] = -prices[0]
(j
为任意奇数)dp[0][j] = 0
(j
为任意偶数) - 遍历顺序:同理,
dp[i]
依靠dp[i-1]
的数值,从前向后遍历 - 举例推导:以输入[1,2,3,4,5],k=2为例。
代码实现
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
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];
}
};
309. 买卖股票的最佳时机含冷冻期
给定一个整数数组prices
,其中第 prices[i]
表示第 <em>i</em>
天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [1,2,3,0,2] 输出: 3 解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
思路
同样从股票买卖在每一天所有可能状态的角度分析
难点在于加入冷静期,卖出状态后的一段时间,可选的状态变为了两种:“主动保持卖出状态不持有” 和 “冷静期无法操作”,其中冷冻期状态不可持续,仅存在一天!
状态转换图如上,具体可以区分出如下四个状态:
- 状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
- 不持有股票状态,这里就有两种卖出股票状态
- 状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
- 状态三:今天卖出股票
- 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!
- dp数组及其下标含义:同理,第
i
天的状态为j
,其最大资金为dp[i][j]
- 递推公式:根据状态转换图分析:
- 状态一:持有状态,分为 “顺沿前一天持有” 和 “今天买入”,由于冷静期,“今天买入”的前一天状态又可分为“前一天为冷冻期” 和 “前一天主动未持有”
- 状态二:未持有状态,分为 “顺沿前一天未持有” 和 “前一天为冷静期”
- 状态三:卖出状态,只有一种可能,前一天为状态一持有状态
- 状态四:冷冻期,只有一种可能,前一天为状态三卖出状态
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];
除了之前出现过的持有和未持有状态 + 题目要求的冷静期,还新引入了一个卖出状态,它是冷冻期的特定上一状态,因未持有状态出现分支而定义
- 数组初始化:同
dp[0][0] = -prices[i];
dp[0][1] = 0;
- 遍历顺序:同为从前向后遍历
- 举例推导:以 [1,2,3,0,2] 为例,dp数组如下:
最后只要是非持有股票的状态,都有可能是最大值,因此最终输出要从最后一天的状态二三四中取最大值。
代码实现
class Solution {
public:
int maxProfit(vector<int>& prices) {
vector<vector<int>>dp(prices.size(), vector<int>(4,0));
dp[0][0] = -prices[0];
for(int i=1; i<prices.size(); 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[prices.size()-1][1], max(dp[prices.size()-1][2], dp[prices.size()-1][3]));
}
};
714. 买卖股票的最佳时机含手续费
给定一个整数数组 prices
,其中 prices[i]
表示第 i
天的股票价格 ;整数 fee
代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意: 这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
示例 1:
输入: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
思路
本题支持任意次数买卖,跟 122.买卖股票的最佳时机 II 的唯一别就是加入了手续费,其实只要在递推公式位置比较max时,额外减去一个手续费即可
- dp数组及其下标含义:同理,
- 递推公式:
- 数组初始化:取决于第0天的五种状态
- 无操作:易得
dp[0][0] = 0
- 第一轮持有:开局就买入
dp[0][1] = -price[0]
- 第一轮未持有,第二轮持有/未持有:只不过是左右倒手,反复买卖:
dp[0][2] = dp[0][4] = 0;
dp[0][3] = dp[0][1] = -price[0]
- 无操作:易得
- 遍历顺序:同理为从前向后
- 举例推导:以一个递增数列[1,2,3,4,5]为例:
代码实现
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int n = prices.size();
vector<vector<int>>dp(n, vector<int>(2,0));
dp[0][0] = -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]-fee);
}
return dp[n-1][1];
}
};
后记
股票买卖问题的核心还是在于状态的分解,本组题目限定可选且仅能持有一支股票,因此每个时刻的可选状态都能够划分为一个有限集合(是否持有?顺沿持有还是当天购入持有?),此时只要确定好初始状态,自i-1
向i
逐步推算即可
今天本组题目对买卖股票问题进行了复杂化,主要从三个方向:
- 买卖次数为任意数——改为构建具有编号规则的
j
种状态,而非固定5
种 - 加入冷静期——引入新的状态转移,影响到了未持有状态中的顺沿和买入,虽固定个数但需要重新设计
- 加入手续费——最简单,只需要为当天卖出的式子后面减去一个手续费即可