一、题目
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
二、思路——动态规划
(1)问题拆解:
题目中冷冻期的意思是,卖出股票后,你无法在第二天买入股票 。也就是说,要想知道今天的持股状态,就要知道前一天的状态是什么。
对于第 i 天,有持股和不持股两种状态。其中,持股有两种可能,一种是昨天就是持股,今天继承昨天的,另一种是今天买入了股票;而不持股则有两种可能,一种是今天卖出了股票所以不持有,另一种是昨天卖出了股票所以今天不持有。总的来说,今天的状态可以分为三种情况,我们分别用 0、1、2 来表示:
- 0——持股;
- 1——前一天就没有股票,今天也没买,所以今天不持股;
- 2——今天卖出股票,所以不持股。
(2)定义状态:
问题拆解中我们分析出了每一天的持股状态,也就可以表示出第 i 天的收益,所以我们定义状态为:第 i 天的收益。我们定义二维状态数组 dp[i][j] 存储每天的收益,其中 i 表示天数,j = 0,1,2 表示这一天的持股状态。
(3)推导状态转移方程:
每一天的持股状态有三种情况,下面我们就分析每种情况下,当天的收益是多少。
- 第 i 天持股:dp[i][0]。一种是昨天就持股,今天继承昨天的,则有 dp[i][0] = dp[i - 1][0];一种是今天买入了股票,前提是昨天没有卖,也就是昨天处于 “不持股且没有卖出” 这个状态下,今天才能买,则有 dp[i][0] = dp[i - 1][1] - prices[i]。所以
d p [ i ] [ 0 ] = m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] − p r i c e s [ i ] ) dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]) dp[i][0]=max(dp[i−1][0],dp[i−1][1]−prices[i]) - 第 i 天不持股,因为第 i - 1 天就没有股票,今天也没买:dp[i][1]。第 i - 1 天没有股票就是问题拆解中的两种情况,则第 i 天的收益为
d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] ) dp[i][1] = max(dp[i - 1][1], dp[i - 1][2]) dp[i][1]=max(dp[i−1][1],dp[i−1][2]) - 第 i 天不持股,因为今天卖出了股票:dp[i][2]。今天卖出了股票,说明昨天一定是持股的,则
d p [ i ] [ 2 ] = d p [ i − 1 ] [ 0 ] + p r i c e s [ i ] dp[i][2] = dp[i - 1][0] + prices[i] dp[i][2]=dp[i−1][0]+prices[i]
(4)寻找边界条件:
以第一天作为初始状态,对dp数组初始化。
- dp[0][0] = -1*prices[0];// 第一天只买入
- dp[0][1] = 0;// 第一天本来就不持有
- dp[0][2] = 0;//可以理解成第一天买入又卖出,那么第一天就是“不持股且当天卖出了”这个状态了,其收益为 0,所以初始化为 0。
三、代码
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
if(n < 2)
return 0;
int[][] dp = new int[n][3];
// 初始化
dp[0][0] = -1 * prices[0];
dp[0][1] = 0;
dp[0][2] = 0;
for(int i = 1; i < n; i++){
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 - 1][2]);
dp[i][2] = dp[i - 1][0] + prices[i];
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
}
}