首先我们知道动态的规划的核心就是穷举状态,然后我们在状态中选择最优解,对于卖卖股票的系列问题,我们就举出可能会出现的所有状态
- 当前是否持有股票,1表示当前持有股票,0表示当前不持有股票
- 当前天数,表示这是第几天的结果,我们求的应该是最后一天的最大利润
- 进行买卖的次数,可能会限制卖卖的次数
for 状态1 in 状态1的所有取值: for 状态2 in 状态2的所有取值: for ... dp[状态1][状态2][...] = 择优(选择1,选择2...)
- 对应着不同的状态说明是有着不同的选择,有三种选择,买入,卖出,无操作,用buy,sell,rest来表示三种选择
- 选择是有限制的,比如我们的buy必须在sell之后,sell必须在bug之后,rest操作也应该分为两种状态,一种是buy之后rest(持有股票),一种是sell之后rest(没有持有股票),而且对应着交易次数的限制,也就是说buy在k>0的前提下操作
穷举框架
dp[i][k][0 or 1] 0 <= i <= n - 1, 1 <= k <= K n 为天数,大 K 为交易数的上限,0 和 1 代表是否持有股票。 此问题共 n × K × 2 种状态,全部穷举就能搞定。 for 0 <= i < n: for 1 <= k <= K: for s in {0, 1}: dp[i][k][s] = max(buy, sell, rest)
- 我们最终想求的答案是dp[n-1][K][0],也就是最后一天,最多允许K次交易,最多获取多少利润
- 为什么不是dp[n-1][K][0],因为在最后一天,我们将手里股票卖出去的利润肯定比放在手里多
状态转换图
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) max( 今天选择 rest, 今天选择 sell )
- 关于今天我们没有持有股票,可能昨天我们也是没有持有股票,那么就是rest
- 关于今天我们没有持有股票,可能昨天我们是持有股票的,那么就是是sell,我们是在buy是时候减少k,因为我们第一次交易肯定是买股票,因为交易是从
buy
开始,如果buy
的选择不改变交易次数k
的话,会出现交易次数超出限制的的错误。关于base case
dp[-1][...][0] = 0 解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。 dp[-1][...][1] = -infinity 解释:还没开始的时候,是不可能持有股票的。 因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。 dp[...][0][0] = 0 解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。 dp[...][0][1] = -infinity 解释:不允许交易的情况下,是不可能持有股票的。 因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
121买卖彩票的最佳时期
- 套模板,这道题其实就是要求我们k是1
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i]) dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i]) = max(dp[i-1][1][1], -prices[i]) 解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。 现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。 可以进行进一步化简去掉所有 k: dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], -prices[i])
class Solution { public int maxProfit(int[] prices) { int n=prices.length; int dp[][]=new int[n][2]; for(int i=0;i<n;i++){ if (i-1==-1){ dp[i][1]=-prices[i]; dp[i][0]=0; continue; } dp[i][1]=Math.max(dp[i-1][1],-prices[i]); dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); } return dp[n-1][0]; } }
- 也就是
k
为正无穷,但含有交易冷冻期的情况如果
k
为正无穷,那么就可以认为k
和k - 1
是一样的。可以这样改写框架dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i]) dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i]) = max(dp[i-1][k][1], dp[i-1][k][0] - prices[i]) 我们发现数组中的 k 已经不会改变了,也就是说不需要记录 k 这个状态了: 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])
关于冷冻期的改变
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i]) dp[i][1] = max(dp[i-1][1], dp[i-2][0] - prices[i]) 解释:第 i 天选择 buy 的时候,要从 i-2 的状态转移,而不是 i-1 。
class Solution { public int maxProfit(int[] prices) { int n=prices.length; int dp[][]=new int [n][2]; //省略k 因为k为无穷大 //买入的时候需要隔一天 for(int i=0;i<n;i++){ if(i-1==-1){ dp[i][0]=0; dp[i][1]=-prices[i]; continue; } if(i-2==-1){ dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); dp[i][1]=Math.max(dp[i-1][1],0-prices[i]); continue; } dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); dp[i][1]=Math.max(dp[i-1][1],dp[i-2][0]-prices[i]); } return dp[n-1][0]; } }