首先我们先明确状态,本题有多个变体,比如说限制最大交易次数为2,不限制交易次数,是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件。
①确定状态
这个问题,每天都有三种「选择」:买入、卖出、无操作,我们用 buy, sell, rest 表示这三种选择。
②确定选择
像这道题就要求最大交易次数就是1了,股票的最大利润问题一共三个状态----->
这个问题的「状态」有三个,第一个是天数,第二个是允许交易的最大次数,第三个是当前的持有状态(即之前说的 rest 的状态,我们不妨用 1 表示持有,0 表示没有持有)。
而且我们可以用自然语言描述出每一个状态的含义,比如说 dp[3][2][1] 的含义就是:今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。再比如 dp[2][3][0] 的含义:今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。
③ 确定状态转移方程
现在,我们完成了「状态」的穷举,我们开始思考每种「状态」有哪些「选择」,应该如何更新「状态」。
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1])
max( 今天选择 rest, 今天选择 sell )
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1])
max( 今天选择 rest, 今天选择 buy )
k 的定义并不是「已进行的交易次数」,而是「最大交易次数的上限限制」。如果确定今天进行一次交易,且要保证截至今天最大交易次数上限为 k,那么昨天的最大交易次数上限必须是 k - 1。
④ 确定base case
dp[0][...][0] = 0
解释:因为 i 是从 1 开始的,所以 i = 0 意味着还没有开始,这时候的利润当然是 0。
dp[0][...][1] = -infinity
解释:还没开始的时候,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
dp[...][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。
dp[...][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的。
因为我们的算法要求一个最大值,所以初始值设为一个最小值,方便取最大值。
下面就先说说第一种情况k=1时的情形:
class Solution {
public int maxProfit(int[] prices) {
int n=prices.length;
if(n==0) return 0;
int [][]dp=new int [n+1][2];
dp[0][0]=0;
dp[0][1]=-prices[0];
for(int i=1;i<=n;i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], -prices[i-1]);
}
return dp[n][0];
}
}