一.通解
参考:https://leetCode-cn.com/circle/article/qiAgHn/
参考:https://leetCode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/discuss/108870/Most-consistent-ways-of-dealing-with-the-series-of-stock-problems
二.描述
给一个价格数组prices[],index=i表示第i天的股票价格为prices[i],每天最多只能执行一个买或卖的操作,给定最多进行k手交易(一手交易包括一次买和一次卖),求能获得的最大收益,一个人最多同时只能持有一个股票
三.通用递推方程
T[i][k]表示第i天结束时,在第0~i天最多进行k手交易后可以得到的最大收益,与T[i][k]相关的子问题有T[i-1][k],T[i-1][k-1],T[i]][k-1],如何得到递推方程/转移方程
因为一个人买入一个股票前,必须这个人当前持有的股票为0,卖出一个股票前必须当前持有的股票数量为1,所以可以根据第i天可以执行的操作:buy/sell/rest来讨论
T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i])
T[i][k][0] = max(T[i-1][k][0], T[i-k][k][1] + prices[i])
最后肯定是T[i][k][0]的收益最大,因为肯定是卖得越多收益越高
注意: T[i][0][0]和T[i][0][1]都应该是0,因为不允许进行一手交易
四.特殊case
4.1 k=1,只能进行一手交易, leetcode-121
T[i][1][1] = max(T[i-1][1][1], T[i-1][0][0] - prices[i]) = max(T[i-1][1][1], -prices[i])
T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1] + prices[i])
4.2 k=+Infinity(可以进行无数手交易),leetcode-122
则k和k-1是一样的
T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i]) = max(T[i-1][k][1], T[i-1][k][0] - prices[i])
T[i][k][0] = max(T[i-1][k][0], T[i-k][k][1] + prices[i])
4.3 k=2,可以进行2手交易, leetcode-123
T[i][2][1] = max(T[i-1][2][1], T[i-1][1][0]-prices[i])
T[i][2][0] = max(T[i-1][2][0], T[i-1][2][1]+prices[i])
T[i][1][1] = max(T[i-1][1][1], T[i-1][0][0]-prices[i])=max(T[i-1][1][1], -prices[i])
T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1]+prices[i])
4.4 k为给定值, leetcode-188
k可以为0、1、2等
优化: 注意到一个事实,就是一手交易至少需要两天(一天买, 一天卖), 所以当k>=n/2(n为prices数组长度)时, k等价于无穷大,
当k
4.5 k=+Infinity且有1天冷却期, leetcode-309
冷却期:昨天卖了股票,今天不能买,必须明天才可以买
所以如果想要第i天买股票,则T[i-1][k][0]必须保证没有在第i-1天卖股票,既第i-1天休息,所以T[i-1][k][0] = T[i-2][k][0]
T[i][k][1] = max(T[i-1][k][1], T[i-2][k][0]- prices[i])
T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
4.6 k=+Infinity且交易费用, leetcode-714
每笔交易都需要手续费, 可以在买入或卖出时扣除手续费
T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i] - fee)
T[i][k][0] = max(T[i-1][k][0], T[i-k][k][1] + prices[i])
因为一个股票的价格可能低于交易手续费fee,所以要取T[i][k][0]和T[i][k][1]的较大值
五.leetcode题解
5.1 leetcode-121,一手交易
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,只能完成一手交易,求所能获取的最大利润。
方法1:DP,通解方程
// 可参与的股票交易数k=1// T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1]+price[i])// T[i][1][1] = max(T[i-1][1][1], T[i-1][0][0]-price[i]) = max(T[i-1][1][1], -price[i])// 设T[i][1][0]为t0, T[i][1][1]为t1public int maxProfit(int[] prices) {if(prices==null || prices.length==0){return 0;}// 第0天要结束时手里有0个股票则只能选择rest// 第0天要结束时手里有1个股票则只能选择买当天股票int t0 = 0, t1 = -prices[0];for(int i=1;i<prices.length;i++){t0 = Math.max(t0, t1+prices[i]);t1 = Math.max(t1, -prices[i]);}return t0;}
方法2:贪心,得到prices[i]之前的历史最低价格minPrice,并认为买入价格就是历史最低价格,然后以prices[i]卖出即可,得到全局最大收益。
5.2 leetcode-122,无数手交易
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,可以尽可能地完成更多的交易(多次买卖一支股票),求所能获取的最大利润。
// 可参与的股票交易数k=+Infinity// T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1]+price[i])// T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0]-price[i])// 设T[i][k][0]为t0, T[i][k][1]为t1public int maxProfit(int[] prices) {// 第0天结束时, 手里要有0个股票则只能休息, 手里要有1个股票则只能买一个股票int t0 = 0, t1 = -prices[0];for(int i=1;i<prices.length;i++){t0 = Math.max(t0, t1+prices[i]);t1 = Math.max(t1, t0-prices[i]);}return t0;}
5.3 leetcode-123,两手交易
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,最多完成两手交易,求所能获取的最大利润。
// T[i][2][1] = max(T[i-1][2][1], T[i-1][1][0]-prices[i])// T[i][2][0] = max(T[i-1][2][0], T[i-1][2][1]+prices[i])// T[i][1][1] = max(T[i-1][1][1], T[i-1][0][0]-prices[i])=max(T[i-1][1][1], -prices[i])// T[i][1][0] = max(T[i-1][1][0], T[i-1][1][1]+prices[i])// 设t10代表T[i][1][0] ,t11代表T[i][1][1], t20代表T[i][2][0], t21代表T[i][2][1; 更新顺序t20->t21->t10->t11, 因为公式里的依赖顺序public int maxProfit(int[] prices) {if(prices==null || prices.length==0){return 0;}int t10 = 0, t11=-prices[0], t20 = 0, t21= -prices[0];for(int i=1;i<prices.length;i++){t20 = Math.max(t20, t21+prices[i]);t21 = Math.max(t21, t10-prices[i]);t10 = Math.max(t10, t11+prices[i]);t11 = Math.max(t11, -prices[i]);}return t20;}
5.4 leetcode-188,k手交易
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,最多可以完成K手交易,求所能获取的最大利润。
//优化: 注意到一个事实,就是一手交易至少需要两天(一天买, 一天卖), 所以当k>=n/2(n为prices数组长度)时, k等价于无穷大,// 当k<n/2时,就可以定义一个int[] v0,int[] v1,表示第i天结束的有0个stock、有1个stock的收益// 更新顺序就从k更大的向k更小的更新,一样的k时从0->1更新。public int maxProfit(int k, int[] prices) {if(prices==null||prices.length==0){return 0;}// k等价于无穷大if(k>prices.length/2){// T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1]+prices[i])// T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0]-prices[i]) = max(T[i-1][k][1], T[i-1][k][0]-prices[i]);int t0 = 0, t1 = -prices[0];for(int i=1;i<prices.length;i++){t0 = Math.max(t0, t1+prices[i]);t1 = Math.max(t1, t0-prices[i]);}return t0;}// v0[j]表示允许最多j手事务第i天剩下0个stock的最大收益int[] v0 = new int[k+1];// v1[j]表示允许最多j手事务第i天剩下1个stock的最大收益int[] v1 = new int[k+1];// 初始化第0天允许i手事务剩下1个stock的收益for(int i=1;i<=k;i++){v1[i] = -prices[0];}// 更新顺序从k大的->k小的, 同一个k从0->1,可以写k=2的四个方程来总结规律for(int i=1;i<prices.length;i++){for(int j=k;j>=1;j--){v0[j] = Math.max(v0[j], v1[j]+prices[i]);v1[j] = Math.max(v1[j], v0[j-1]-prices[i]);}}return v0[k];}
5.5 leetcode-309,有一天冷静期
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,可以尽可能地完成更多的交易(多次买卖一支股票),但有一天冷静期(比如第i-1天卖出的股票,第i天就不能买入股票),求所能获取的最大利润。
// T[i][k][1] = max(T[i-1][k][1], T[i-2][k][0]- prices[i])// T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])public int maxProfit(int[] prices) {if(prices==null || prices.length==0){return 0;}// 因为第i天的T[i][k][1]需要第i-2天的T[i-2][k][0], 所以// 初始化第0天的T[0][k][0], T[0][k][1]int t0 = 0, t1 = -prices[0];// 初始化第-1天的T[-1][k][0]int tBefore0 = 0;for(int i=1;i<prices.length;i++){int temp = t0;// 更新T[i][k][0]t0 = Math.max(t0, t1+prices[i]);t1 = Math.max(t1, tBefore0-prices[i]);tBefore0 = temp;}return t0;}
5.5 leetcode-714,每手交易要手续费
题目:prices[i]股票数组,prices[i] 是一支给定股票第 i 天的价格,可以尽可能地完成更多的交易(多次买卖一支股票),但每手交易都需要手续费,求所能获取的最大利润。
// 设买入时扣除手续费// T[i][k][1] = max(T[i-1][k][1], T[i-1][k][0] - prices[i] - fee)// T[i][k][0] = max(T[i-1][k][0], T[i-k][k][1] + prices[i])public int maxProfit(int[] prices, int fee) {if(prices==null || prices.length==0){return 0;}int t0 = 0, t1 = -prices[0]-fee;for(int i=1;i<prices.length;i++){t0 = Math.max(t0, t1+prices[i]);t1 = Math.max(t1, t0-prices[i]-fee);}// 因为一个股票的价格可能低于交易手续费fee,所以要取t0和t1的较大值return Math.max(t0, t1);}
