给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
一、思路
先来说说这道题目的难点:
- 冷冻期
如何处理冷冻期是关键,一般来说,会使用动态规划来计算此类问题。
(一)动态规划
按照之前的思路,我使用两个动态规划表来做,结果发现行不通:
我们考虑两种情况:
- 不在冷冻期内购买(dp1)。
- 在冷冻期内购买(dp2),此时,出售日期需要提前一天,也就是说虽然本次交易的利润并非最大利润,但是我们将赌注压在了冷冻期上。
于是代码逻辑就清晰,我们设置2个数组:
- dp1[i]:交易到第i天为止的最大利润
- dp2[i]:交易到第i天为止,且明天仍然能够购买股票,所能获取的最大利润
但是这个思路有个很大的限制,就是明天购买的问题,对于不同的区间,明天购买这个操作所能带来的利润是不一样的,我们没办法通过计算一个一个的小区间来
最后参考了一下解题,写的很好:一个方法团灭 6 道股票问题
上面说的是一个泛化模型,这里针对这道题目,详细展开一下。
1、状态转移框架
这里先建立一个动态规划表 d p [ i ] [ j ] dp[i][j] dp[i][j]
- i:表示天数,第i天
- j:表示现在持有股票的状态,0表示没有持有股票,1表示持有股票
动态规划表 d p [ i ] [ j ] dp[i][j] dp[i][j]的意义就很明显了,就在第i天时,手里持有股票(j=1)或者没有持股(j=0)所能获取的最大利润。
除此之外,在这一天,还有3种操作可选:buy、sell、rest。
我们现在要做的就是遍历所有可能的状态,求出利润最大的,那么这里的解题伪代码可以写成:
for 0 <= i < n:
for j in {0, 1}:
dp[i][j] = max{buy, sell, rest}
这是状态转移图:
于是所求的答案为:
d
p
[
n
−
1
]
[
0
]
dp[n - 1][0]
dp[n−1][0]
2、状态转移方程
-
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天,手里没有持股,所能获取的最大利润为 d p [ i ] [ 0 ] dp[i][0] dp[i][0]
那么这个利润是怎么来的?这要从当前的状态分析,因为当前没有持股,要么昨天没有持股,要么昨天持股,但是今天卖掉了。 -
d
p
[
i
]
[
1
]
=
m
a
x
(
d
p
[
i
−
1
]
[
1
]
,
d
p
[
i
−
1
]
[
0
]
−
p
r
i
c
e
s
[
i
]
)
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
dp[i][1]=max(dp[i−1][1],dp[i−1][0]−prices[i])
这个方程表达的意思是:在第i天,手里持股,所能获取的最大利润为 d p [ i ] [ 1 ] dp[i][1] dp[i][1]
那么这个利润是怎么来的?这要从当前的状态分析,因为当前持股,要么昨天持股,要么昨天没有持股,但是今天买入了股票。这个方程存在一个小小的问题。
这里注意一下,因为股票卖出后,有一天的冷冻期不能购买,这里对
d
p
[
i
−
1
]
[
0
]
−
p
r
i
c
e
s
[
i
]
dp[i - 1][0] - prices[i]
dp[i−1][0]−prices[i]操作是否合法,也需要分析。这就涉及到
d
p
[
i
−
1
]
[
0
]
dp[i - 1][0]
dp[i−1][0]是通过哪种方法得到的,代入公式替换之得:
m
a
x
(
d
p
[
i
−
2
]
[
0
]
,
d
p
[
i
−
2
]
[
1
]
+
p
r
i
c
e
s
[
i
−
1
]
)
−
p
r
i
c
e
s
[
i
]
max(dp[i - 2][0], dp[i - 2][1] + prices[i - 1]) - prices[i]
max(dp[i−2][0],dp[i−2][1]+prices[i−1])−prices[i]
显然只有 d p [ i − 2 ] [ 0 ] dp[i - 2][0] dp[i−2][0]的情况下,该操作才合法,于是我们可以直接将 d p [ i − 1 ] [ 0 ] dp[i - 1][0] dp[i−1][0]替换为 d p [ i − 2 ] [ 0 ] dp[i - 2][0] dp[i−2][0]来确保操作一定是合法的。
于是修改第二个动态规划方程:
- d p [ i ] [ 1 ] = m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 2 ] [ 0 ] − p r i c e s [ i ] ) dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i]) dp[i][1]=max(dp[i−1][1],dp[i−2][0]−prices[i])
实际上这两个方程可以这么解释:
- 更新持股利润时等价于:在已有的最大利润基础上,尽可能的挑选低价股票购买
- 更新不持股利润时等价于:在已有的最大利润基础上,尽可能的将股票以高价卖出。
C++代码:
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.size() == 0 || prices.size() == 1)
return 0;
vector<vector<int>> dp(prices.size(), vector<int>(2, 0));
// 初始化
dp[0][1] = -prices[0];
dp[1][1] = (-prices[0] > -prices[1]) ? -prices[0] : -prices[1];
dp[1][0] = max(dp[0][0], dp[0][1] + prices[1]);
// 动态规划
for(int i=2; i < prices.size(); 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]);
}
return dp[prices.size() - 1][0];
}
};