【Leetcode】DP | 买卖股票的最佳时机,DP居然还可以用状态机?

带状态的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(i1), 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(i1)

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(i1), 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(i1), 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(i1), 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(i1),prices[i]+sellk1(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(i1),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 i1天的 <持有状态 + 不卖动作> 和第 i − 1 i-1 i1天的 <未持有状态 + 买入动作> 转移而来
    • <持有状态 + 不卖动作> : D [ i − 1 ] [ 1 ] D[i-1][1] D[i1][1]
    • <未持有状态 + 买入动作>: − p r i c e s [ i ] + D [ i − 1 ] [ 0 ] -prices[i] + D[i-1][0] prices[i]+D[i1][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[i1][1],prices[i]+D[i1][0])
  • i i i天若为未持有状态,可由第 i − 1 i-1 i1天的 <冷冻期 + 过一天> 和第 i − 1 i-1 i1天的 <未持有状态 + 不买动作> 转移而来
    • <冷冻期 + 过一天> : D [ i − 1 ] [ 2 ] D[i-1][2] D[i1][2]
    • <未持有状态 + 不买动作>: D [ i − 1 ] [ 0 ] D[i-1][0] D[i1][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[i1][0],D[i1][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[i1][1]+prices[i]

【Leetcode】DP | 买卖股票的最佳时机,DP居然还可以用状态机?_第1张图片

代码:

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 买卖股票的最佳时机含手续费

和Ⅱ蛮像,状态机确实好使。

【Leetcode】DP | 买卖股票的最佳时机,DP居然还可以用状态机?_第2张图片

有手续费的话就不会出现当日买入、当日卖出的情况啦,负收益捏。

  1. i i i天为持有状态的最大收益:
    • <未持有 + 买入>: − p r i c e s [ i ] + D [ i − 1 ] [ 1 ] -prices[i] + D[i-1][1] prices[i]+D[i1][1]
    • <持有 + 不卖>: D [ i − 1 ] [ 0 ] D[i-1][0] D[i1][0]
  2. 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[i1][0]
    • <未持有 + 不卖>: D [ i − 1 ] [ 1 ] D[i-1][1] D[i1][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;
    }
}

你可能感兴趣的:(Leetcode题解总结,leetcode,算法)