带状态的DP君~
类型总结:买卖一次、买卖无限次、买卖k次、买卖无限次、含冷冻期。
买卖k次的问题需要不断统计、维护买卖i次的最大收益。
状态较多的题可以借助状态机分析状态转移情况。
统计第 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;
}
}
贪心(不好意思啦作弊啦),如果当前股票价格比前一天高,那就在前一天买入、在今天卖出。
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;
}
}
思路可能不难,但细节确实太多。
以第一次买入、第一次卖出、第二次买入、第二次卖出作为4个状态。
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
需要的统计量包括:
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天及之前第一次卖出股票能得到的最大收益
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;
}
}
最多可以买 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];
}
}
分析不明白了捏,画个状态机看看。
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天在未持有、持有、冷冻期状态的最大收益。
代码:
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;
}
}
和Ⅱ蛮像,状态机确实好使。
有手续费的话就不会出现当日买入、当日卖出的情况啦,负收益捏。
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;
}
}