带状态的DP君~
-
类型总结:买卖一次、买卖无限次、买卖k次、买卖无限次、含冷冻期。
-
买卖k次的问题需要不断统计、维护买卖i次的最大收益。
-
状态较多的题可以借助状态机分析状态转移情况。
121 买卖股票的最佳时机
统计第 i i i天之前的股票最低价格,计算在第 i i i天卖出时能得到的最大收益,从而得到全局的最大收益。一次遍历可以解决。
class Solution {
public int maxProfit(int[] prices) {
// 目前的最小价格
int minPrice = prices[0];
// 能够获得的最大收益
int profit = 0;
for (int i = 1; i < prices.length; i++) {
// 更新收益
profit = Math.max(profit, prices[i] - minPrice);
// 更新价格
minPrice = Math.min(minPrice, prices[i]);
}
return profit;
}
}
122 买卖股票的最佳时机Ⅱ
贪心(不好意思啦作弊啦),如果当前股票价格比前一天高,那就在前一天买入、在今天卖出。
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
profit += Math.max(0, prices[i] - prices[i - 1]);
}
return profit;
}
}
123 买卖股票的最佳时机Ⅲ ★
思路可能不难,但细节确实太多。
以第一次买入、第一次卖出、第二次买入、第二次卖出作为4个状态。
先考虑在第i天时的情况
D[i][0] 在第i天第一次买入股票的收益: -prices[i]
D[i][1] 在第i天第一次卖出股票的最大收益: prices[i] - min(prices[j]), j <= i
D[i][2] 在第i天第二次买入股票的最大收益: -prices[i] + max(D[j][1]), j <= i
D[i][3] 在第i天第二次卖出股票的最大收益: prices[i] + max(D[j][2]), j <= i
在考虑在第i天及之前的情况
需要的统计量包括:
1、第
i
i
i天之前股票的最小价格
minPrice
(
i
)
=
min
(
minPrice
(
i
−
1
)
,
p
r
i
c
e
s
[
i
]
)
\text{minPrice}(i) = \min(\text{minPrice}(i - 1),\ prices[i])
minPrice(i)=min(minPrice(i−1), prices[i])
2、第
i
i
i天及之前第一次卖出股票能得到的最大收益
- 可以在同一天先买入后卖出,至少这样第一笔交易不会是负收益,故减去 m i n P r i c e ( i ) minPrice(i) minPrice(i),而不是减去 m i n P r i c e ( i − 1 ) minPrice(i - 1) minPrice(i−1)
sell 1 ( i ) = max ( sell 1 ( i − 1 ) , p r i c e s [ i ] − minPrice ( i ) ) \text{sell}_1(i) = \max(\text{sell}_1(i-1),\ prices[i] - \text{minPrice}(i)) sell1(i)=max(sell1(i−1), prices[i]−minPrice(i))
3、第
i
i
i天及之前第二次买入股票能得到的最大收益
buy
2
(
i
)
=
max
(
buy
2
(
i
−
1
)
,
−
p
r
i
c
e
s
[
i
]
+
sell
1
(
i
)
)
\text{buy}_2(i) = \max(\text{buy}_2(i-1),\ -prices[i] + \text{sell}_1(i))
buy2(i)=max(buy2(i−1), −prices[i]+sell1(i))
4、第
i
i
i天及之前第二次卖出股票能得到的最大收益:
sell
2
(
i
)
=
max
(
sell
2
(
i
−
1
)
,
p
r
i
c
e
s
[
i
]
+
buy
2
(
i
)
)
\text{sell}_2(i) = \max(\text{sell}_2(i-1),\ prices[i] + \text{buy}_2(i))
sell2(i)=max(sell2(i−1), prices[i]+buy2(i))
写成代码类似物,变量的更新方式如下:
第i天及之前:
股票的最小价格:buy1 = min(buy1, prices[i])
第一次卖出股票能得到的最大收益:sell1 = max(sell1, prices[i] - buy1)
第二次买入股票的最大收益:buy2 = max(buy2, -prices[i] + sell1)
第二次卖出股票能获得的最大收益:sell2 = max(sell2, prices[i] + buy2)
完整代码,代码非常简介,分析非常复杂:
class Solution {
public int maxProfit(int[] prices) {
int minPrice = Integer.MAX_VALUE;
int sell1 = Integer.MIN_VALUE;
int buy2 = Integer.MIN_VALUE;
int sell2 = Integer.MIN_VALUE;
for (int i = 0; i < prices.length; i++) {
minPrice = Math.min(prices[i], minPrice);
sell1 = Math.max(sell1, prices[i] - minPrice);
buy2 = Math.max(buy2, -prices[i] + sell1);
sell2 = Math.max(sell2, prices[i] + buy2);
}
return sell2;
}
}
188 买卖股票的最佳时机Ⅳ
最多可以买 k k k次啦,建议写完Ⅲ再来写Ⅳ,要不真的思考过程too hard。
第
i
i
i天及之前第k次买入的最大收益:
b
u
y
k
(
i
)
=
max
(
b
u
y
k
(
i
−
1
)
,
−
p
r
i
c
e
s
[
i
]
+
s
e
l
l
k
−
1
(
i
)
)
buy_k(i) = \max(buy_k(i - 1), -prices[i]+sell_{k-1}(i))
buyk(i)=max(buyk(i−1),−prices[i]+sellk−1(i))
第
i
i
i天及之前第k次卖出的最大收益:
s
e
l
l
k
(
i
)
=
max
(
s
e
l
l
k
(
i
−
1
)
,
p
r
i
c
e
s
[
i
]
+
b
u
y
k
(
i
)
)
sell_k(i)=\max(sell_k(i-1), prices[i]+buy_k(i))
sellk(i)=max(sellk(i−1),prices[i]+buyk(i))
所以需要一个数组捏:int[][] D = new int[k + 1][2]
遍历整个数组,遍历到每一个元素时,更新D。
D[k][0] = max(D[k][0], -prices[i] + D[k - 1][1])
D[k][1] = max(D[k][1], prices[i] + D[k][0])
初始化:
D[k][0] = -inf
D[k][1] = 0
完整代码:
class Solution {
public int maxProfit(int k, int[] prices) {
// D[j][0]表示第j次买入的最大收益,D[j][1]表示第j次卖出的最大收益,j从1到k
int[][] D = new int[k + 1][2];
for (int j = 0; j <= k; j++) {
D[j][0] = Integer.MIN_VALUE;
}
for (int price : prices) {
for (int j = 1; j <= k; j++) {
D[j][0] = Math.max(D[j][0], -price + D[j - 1][1]);
D[j][1] = Math.max(D[j][1], price + D[j][0]);
}
}
return D[k][1];
}
}
309 最佳买卖股票时机含冷冻期
分析不明白了捏,画个状态机看看。
D [ i ] [ 0 ] , D [ i ] [ 1 ] , D [ i ] [ 2 ] D[i][0], D[i][1], D[i][2] D[i][0],D[i][1],D[i][2]表示第 i i i天在未持有、持有、冷冻期状态的最大收益。
- 第
i
i
i天若为持有状态,可由第
i
−
1
i-1
i−1天的 <持有状态 + 不卖动作> 和第
i
−
1
i-1
i−1天的 <未持有状态 + 买入动作> 转移而来
- <持有状态 + 不卖动作> : D [ i − 1 ] [ 1 ] D[i-1][1] D[i−1][1]
- <未持有状态 + 买入动作>: − p r i c e s [ i ] + D [ i − 1 ] [ 0 ] -prices[i] + D[i-1][0] −prices[i]+D[i−1][0]
- D [ i ] [ 1 ] = max ( D [ i − 1 ] [ 1 ] , − p r i c e s [ i ] + D [ i − 1 ] [ 0 ] ) D[i][1] = \max(D[i-1][1], -prices[i] + D[i-1][0]) D[i][1]=max(D[i−1][1],−prices[i]+D[i−1][0])
- 第
i
i
i天若为未持有状态,可由第
i
−
1
i-1
i−1天的 <冷冻期 + 过一天> 和第
i
−
1
i-1
i−1天的 <未持有状态 + 不买动作> 转移而来
- <冷冻期 + 过一天> : D [ i − 1 ] [ 2 ] D[i-1][2] D[i−1][2]
- <未持有状态 + 不买动作>: D [ i − 1 ] [ 0 ] D[i-1][0] D[i−1][0]
- D [ i ] [ 0 ] = max ( D [ i − 1 ] [ 0 ] , D [ i − 1 ] [ 2 ] ) D[i][0] = \max(D[i-1][0], D[i-1][2]) D[i][0]=max(D[i−1][0],D[i−1][2])
- 第
i
i
i天若为冷冻期:
- <持有状态 + 卖出>: D [ i ] [ 2 ] = D [ i − 1 ] [ 1 ] + p r i c e s [ i ] D[i][2] = D[i-1][1]+prices[i] D[i][2]=D[i−1][1]+prices[i]
代码:
class Solution {
public int maxProfit(int[] prices) {
int hold = Integer.MIN_VALUE;
int freeze = 0;
int notHold = 0;
int profit = Integer.MIN_VALUE;
for (int i = 0; i < prices.length; i++) {
hold = Math.max(hold, -prices[i] + notHold);
notHold = Math.max(notHold, freeze);
freeze = hold + prices[i];
profit = Math.max(profit, freeze);
}
return profit;
}
}
714 买卖股票的最佳时机含手续费
和Ⅱ蛮像,状态机确实好使。
有手续费的话就不会出现当日买入、当日卖出的情况啦,负收益捏。
- 第
i
i
i天为持有状态的最大收益:
- <未持有 + 买入>: − p r i c e s [ i ] + D [ i − 1 ] [ 1 ] -prices[i] + D[i-1][1] −prices[i]+D[i−1][1]
- <持有 + 不卖>: D [ i − 1 ] [ 0 ] D[i-1][0] D[i−1][0]
- 第
i
i
i天为未持有状态的最大收益:
- <持有 + 卖出>: p r i c e s [ i ] − f e e + D [ i − 1 ] [ 0 ] prices[i] -fee + D[i-1][0] prices[i]−fee+D[i−1][0]
- <未持有 + 不卖>: D [ i − 1 ] [ 1 ] D[i-1][1] D[i−1][1]
class Solution {
public int maxProfit(int[] prices, int fee) {
int hold = Integer.MIN_VALUE;
int notHold = 0;
int profit = 0;
for (int i = 0; i < prices.length; i++) {
hold = Math.max(hold, -prices[i] + notHold);
notHold = Math.max(notHold, prices[i] - fee + hold);
// 更新最大收益
profit = Math.max(profit, notHold);
}
return profit;
}
}