代码随想录笔记_动态规划
代码随想录二刷笔记记录
LC123.买卖股票的最佳时机III
题目
股票问题
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:
输入:prices = [1]
输出:0
思路分析
思路:
本题和LC121,123一样,仍然存在两个基本状态,持股与不持股,由于题目规定,最多可以完成两笔交易。根据两笔交易,可以将两个基本状态进行拆分。
上述两种基本状态拆分为:
- 第一次持股
- 第一次不持股
- 第二次持股
- 第二次不持股
而考虑到示例3的输出,没有利润的情况下,选择不操作(空仓)。因此,还需考虑一种状态:不操作
注意到,题目要求不能参与多笔交易,因此状态的顺序一定是: 第一次持股 - 第二次持股 - 第三次持股 - 第四次持股
动态规划五部曲
1.确定dp数组及其下标的含义
dp[i][j] : i 表示第 i 天, j 为[0-4] 五个状态 , dp[i][j] 第 i 天,状态 j 的最大利润
0 : 没有操作
1 : 第一次买入
2 : 第一次卖出
3 : 第二次买入
4 : 第二次卖书
2.确定递推公式
回顾LC122,我们可知,因为股票问题至多只能买入一股,具有满仓(持有股票的状态),空仓(不持有股票的状态); 而买入和卖出操作,都是状态转移的操作。
dp[i][1] : 第 i 天买入股票的状态
dp[i][1] 由 dp[i][0] 推演而来
// 第 i 天买入股票
//状态转移:空仓 -> 满仓 , i-1 天的状态0,转移到第 i 天的状态1
dp[i][1] = dp[i-1][0] - prices[i];
// 第 i-1 天就持有股票,保持满仓状态1
dp[i][1] = dp[i-1][1];
dp[i][1] = Max(dp[i-1][0] - prices[i],dp[i-1][1]);
dp[i][2] 由 dp[i][1] 推演而来
//第 i 天卖出股票
//状态转移: 满仓 -> 空仓, i-1 天的状态1,转移到第 i 天的状态2
dp[i][2] = dp[i-1][1] + prices[i];
// 第 i 天没有操作,保持空仓状态2
dp[i][2] = dp[i-1][2];
dp[i][2] = Max(dp[i-1][1] + prices[i],dp[i-1][2]);
dp[i][3] 同理
// 第 i 天买入股票
//状态转移:空仓 -> 满仓 , i-1 天的状态2,转移到第 i 天的状态3
dp[i][3] = dp[i-1][2] - prices[i];
//第 i 天没有操作,保持满仓状态3
dp[i][3] = dp[i-1][3];
dp[i][3] = Max(dp[i-2][3], dp[i-2][2] - prices[i]);
dp[i][4] 同理
//第 i 天卖出股票
//状态转移: 满仓 -> 空仓, i-1 天的状态3,转移到第 i 天的状态4
dp[i][4] = dp[i-1][3] + prices[i];
// 第 i 天没有操作,保持空仓状态4
dp[i][4] = dp[i-1][4];
dp[i][4] = Max(dp[i-1][3] + prices[i], dp[i-1][4]);
3.初始化
dp[0][0] = 0; // 第 0 天没有操作
dp[0][1] = -prices[0]; // 第 0 天买入股票
dp[0][2] = 0;//第 0 天卖出股票
dp[0][3] = -prices[0];//第 0 天第二次买入股票,相当于第 0 天买入卖出又买入
dp[0][4] = 0;//第 0 天第二次卖出股票,相当于第 0 天两次的买卖
4.遍历顺序
根据递推公式,我们知道,第 i 天的状态依赖于第 i-1 天的状态,因此是从前往后 遍历。
for (int i = 1;i < dp.length;i++){
dp[i][0] = Math.max(dp[i][0],dp[i-1][0]);
dp[i][1] = Math.max(dp[i-1][0] - prices[i], dp[i-1][1]);
dp[i][2] = Math.max(dp[i-1][1] + prices[i], dp[i-1][2]);
dp[i][3] = Math.max(dp[i-1][2] - prices[i], dp[i-1][3]);
dp[i][4] = Math.max(dp[i-1][3] + prices[i], dp[i-1][4]);
}
5.推演分析
以示例2 prices = [1,2,3,4,5] 为例
状态0 | 状态1 | 状态2 | 状态3 | 状态4 | |
---|---|---|---|---|---|
i | 不操作 | 买入1 | 卖出1 | 买入2 | 卖出2 |
0 | 0 | -1 | 0 | -1 | 0 |
1 | 0 | max(-1,-2) = -1 | max(-1,-1+2) = 1 | max(-1,-2) = -1 | max(0,-1+2)= 1 |
2 | 0 | max(-1,-3) = -1 | max(-1,-1+3) = 2 | max(-1,1-3) = -1 | max(1,-1+3)= 2 |
3 | 0 | max(-1,-4) = -1 | max(-1,-1+4) = 3 | max(-1,2-4) = -1 | max(2,-1+4)= 3 |
4 | 0 | max(-1,-5) = -1 | max(-1,-1+5) = 4 | max(-1,3-5) = -1 | max(3,-1+5)= 4 |
代码实现
完整代码实现
public int maxProfit(int[] prices) {
//5种状态
//0:没有操作 1:第一次买入 2:第一次卖出 3:第二次买入 4:第二
//dp初始化
int[][] dp = new int[prices.length][5];
//数组初始化时即为0,状态0,2,4可以注释
dp[0][0] = 0;//第 0 天没有操作
dp[0][1] = -prices[0]; //第 0 天买入股票
dp[0][2] = 0; // 第 0 天卖出股票
dp[0][3] = -prices[0];// 第 0 天第二次买入股票
dp[0][4] = 0; // 第 0 天第二次买卖
//遍历
for (int i = 1;i < dp.length;i++){
//dp[i][0]没有操作全为0,可以注释掉
dp[i][0] = Math.max(dp[i][0],dp[i-1][0]); //第 i 天没有操作,第 i-1 天没有操作
//dp[i-1][1] - prices[i]第 i-1 天买入股票,今天继续买,
dp[i][1] = Math.max(dp[i-1][0] - prices[i], dp[i-1][1]);
//dp[i-1][1] + prices[i],第i-1天买入股票.第i天卖出
dp[i][2] = Math.max(dp[i-1][1] + prices[i], dp[i-1][2]);
dp[i][3] = Math.max(dp[i-1][2] - prices[i], dp[i-1][3]);
dp[i][4] = Math.max(dp[i-1][3] + prices[i], dp[i-1][4]);
}
return dp[prices.length-1][5-1];
}
小结
本题增加了3个限制条件
- 最多可完成两笔交易
- 不能参与多笔交易
- 可选择从头到尾不交易(示例3)
因此我们将状态拆分为5种状态,但是本质的递推公式分析,与LC122买卖股票的最佳时机II,需要理解持股和不持股,即满仓和空仓的状态是否会转移。如果存在买入和卖出操作,则会产生状态转移,反之,即保持状态不变即可。本质上的递推不变,终点在于分析题目和拆解状态。