【代码训练营】day51 | 309.最佳买卖股票时机含冷冻期 & 714.买卖股票的最佳时机含手续费 & 买卖股票总结

所用代码 java

买卖股票的最佳时机喊冻结期

题目链接:买卖股票的最佳时机喊冻结期 - 中等

思路

  • dp数组:dp[i] [j],第i天状态为j,最大利润dp[i] [j]
    • dp[i] [0]:持有股票的最大利润
    • dp[i] [1]:保持卖出股票的状态
    • dp[i] [2]:具体卖出股票的状态 – 第二天就是冷冻期
    • dp[i] [3]:冷冻期
  • 递推公式:
    • dp[i][0] = max(dp[i-1][0], dp[i-1][3] - price[i], dp[i-1][1]-price[i])
      • 持有三个状态:前一天持有股票、冷冻期买入、保持卖出时卖出
    • dp[i][1] = max(dp[i-1][1], dp[i-1][3])
      • 保持卖出两个状态:前一天保持卖出、冷冻期
    • dp[i][2] = dp[i-1][0] + price[i]
      • 卖出股票即前一天一定是持有股票
    • dp[i][3] = dp[i-1][2]
      • 冷冻期前一天一定是卖出股票状态
  • 初始化:
    • dp[0][0] = -price[0] 持有股票包含当天买入
    • dp[0][1] = 0 非负状态,无实意,通过递推dp[1][0]=dp[0][1]-price[1]公式发现需要0
    • dp[0][2] = 0 和上面一样,但也可以理解当天买当天卖
    • dp[0][3] = 0 同理
  • 遍历顺序
  • 打印
  • 返回 max(dp[0] [1], dp[0] [2], dp[0] [3]) 卖出三种状态谁赚的多
class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int[][] dp = new int[len][4];
        // 初始化 -- 四个状态
        dp[0][0] = -prices[0]; // 1、持有
        dp[0][1] = 0; // 2、保持卖出
        dp[0][2] = 0; // 3、具体卖出,第二天为冷冻期
        dp[0][3] = 0; // 4、冷冻期
        for (int i = 1; i < len; i++) {
            // 1、持有(前一天持有、前一天保持卖出今日买入,前一天冷冻期今日买入)
            dp[i][0] = Math.max(dp[i-1][0], Math.max(dp[i-1][1] - prices[i], dp[i-1][3] - prices[i]));
            // 2、保持卖出(前一天保持卖出、冷冻期(因为冷冻期第二天没法交易))
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
            // 3、具体卖出(前一天肯定是持有,然后今日卖出)
            dp[i][2] = dp[i-1][0] + prices[i];
            // 4、冷冻期(前一天肯定是具体卖出)
            dp[i][3] = dp[i-1][2];
        }
        // 3种卖出状态最大即为利润最大
        return Math.max(dp[len-1][1], Math.max(dp[len-1][2], dp[len-1][3]));
    }
}

由于所有状态都由前一天的状态推导出来的,所以可以优化为一维的数组以优化空间:

class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[4];
        // 初始化 -- 四个状态
        dp[0] = -prices[0]; // 1、持有
        dp[1] = 0; // 2、保持卖出
        dp[2] = 0; // 3、具体卖出,第二天为冷冻期
        dp[3] = 0; // 4、冷冻期
        for (int i = 1; i < len; i++) {
            // 由于前一天的dp[0]dp[2]都会发生改变,所以需要保存临时变量
            int temp1 = dp[0];
            int temp2 = dp[2];
            // 1、持有(前一天持有、前一天保持卖出今日买入,前一天冷冻期今日买入)
            dp[0] = Math.max(dp[0], Math.max(dp[1] - prices[i], dp[3] - prices[i]));
            // 2、保持卖出(前一天保持卖出、冷冻期(因为冷冻期第二天没法交易))
            dp[1] = Math.max(dp[1], dp[3]);
            // 3、具体卖出(前一天肯定是持有,然后今日卖出)
            dp[2] = temp1 + prices[i];
            // 4、冷冻期(前一天肯定是具体卖出)
            dp[3] = temp2;
        }
        // 3种卖出状态最大即为利润最大
        return Math.max(dp[1], Math.max(dp[2], dp[3]));
    }
}

总结

其实卖出本来就分为两个状态,一是当天就是卖出,二是非当天卖出一直保持卖出的状态,而在前面的题中由于这两者没区别,所以没有区分,这题有了一个冷冻期,非当天卖出则会影响第二天能否买入。

另外,其实我们也可以优化成3个状态:

  • dp[i] [0]:买入,需在冷冻后一天才能买入
  • dp[i] [1]:卖出
  • dp[i] [2]:冷冻期,最大利润为前一天卖出

递推公式:

  • dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i]);
    • 买入两个状态:前一天买入、卖出(冷冻期后一天才能买)
  • dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
    • 卖出两个状态:前一天保持卖出,今天卖出
  • dp[i][2] = dp[i-1][1];
    • 冷冻期一个状态:前一天卖出
class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int[][] dp = new int[len][3];
        // 初始化 -- 四个状态
        dp[0][0] = -prices[0]; // 1、买入, 冷冻期的后一天才可以买入
        dp[0][1] = 0; // 2、卖出
        dp[0][2] = 0; // 3、冷冻期
        for (int i = 1; i < len; i++) {
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2] - prices[i]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i]);
            dp[i][2] = dp[i-1][1];
        }
        return Math.max(dp[len-1][1], dp[len-1][2]);
    }
}

上面也可压缩一维数组。

还有一种思维,和之前的买卖股票3的思想类似,就是把买卖分为一组。

对于不持有(卖出):上一次买卖无影响

对于持有(买入):由于上一次卖出会影响本次买入,所以得去考虑上上次的卖出

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        if (len < 2) return 0; // 只有一个数利润为0
        int[][] dp = new int[len][2];
        // 初始化 -
        dp[0][0] = 0; // 第一次不持有
        dp[0][1] = -prices[0]; // 第一次持有
        dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]); // 第二次不持有(卖出)
        dp[1][1] = Math.max(dp[0][1], -prices[1]); // 第二次持有(买入)
        for (int i = 2; i < len; i++) {
            // 第i次持有:上一次持有 或 上一次卖出
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
            // 第i次不持有:上一次不持有 或 前一次卖出(由于冰冻期上一次卖出不能买入)
            dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i]);
        }
        return dp[len-1][0];
    }
}

另外:还有一种思维,不好理解。

dp数组的含义为:

  • dp[i] [0]:第i天持有股票的收益
  • dp[i] [1]:第i天不持有股票的收益

递推公式:

  • dp[i][0] = max(dp[i-1][0], dp[i-2][1] + price[i-1])
    • dp[i-1][0] 前一天持有
    • 前一天不持有有两个状态
      • i天为冷静期,所以不能以i-1天购买股票,只能在i-2天购买股票
      • i天不为冷静期,证明在i-1天没有进行卖出,所以可以在i-2天进行买入
  • dp[i][1] = max(dp[i-1][1], dp[i-1][0] + price[i-1])
    • 不持有只有两种情况:前一天不持有,或者前一天持有今日卖出
    • price[i-1]是因为前一天持有的状态不管是不是冷静期,都不能在当天进行操作,只能在前一天操作。
class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        int[][] dp = new int[len+1][2];
        // 初始化 -- 2个状态
        dp[1][0] = -prices[0];
        for (int i = 2; i <= len; i++) {
            dp[i][0] = Math.max(dp[i-1][0], dp[i-2][1] - prices[i-1]);
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] + prices[i-1]);
        }
        return dp[len][1];
    }
}

买卖股票的最佳时机含手续费 LeetCode 714

题目链接:买卖股票的最佳时机含手续费 LeetCode 714 - 中等

思路

本题贪心做过,贪心那个思路有点难想,用动态规划简单一点,就是买卖股票2的情况下,卖股票多扣一次手续费,具体可以看之前的买卖股票2链接:买卖股票2.

我们的每个状态都可以由前一个状态得出,这里我直接用一维数组。

  • dp数组
    • dp[0] 持有股票的最大利润
    • dp[1] 不持有
  • 递推公式:
    • dp[0] = max(dp[0], dp[1] - price[i])
      • 持有股票:上一次持有、上次不持有,本次买入
    • dp[1] = max(dp[1], dp[0] + price[i] - fee)
      • 不持有股票:上一次不持有、 上一次持有,本次卖出,还要被扣手续费
  • 初始化:
    • dp[0] = -price[0] 首次持有,买入
    • dp[1] = 0 无意义,不能是 -fee,因为第一天根本没有买卖。
class Solution {
    public int maxProfit(int[] prices, int fee) {
        int[] dp = new int[2];
        // 初始化
        dp[0] = -prices[0]; // 持有
        dp[1] = 0; // 不持有
        for (int i = 1; i < prices.length; i++) {
            dp[0] = Math.max(dp[0], dp[1] - prices[i]);
            dp[1] = Math.max(dp[1], dp[0] + prices[i] - fee);
//            System.out.println("dp[0] =  "+dp[0] + "\tdp[1] = " +dp[1]);
        }
        return dp[1];
    }
}

总结

买卖股票系列已完结,买卖股票通常来说有两个状态:买、卖:

  1. 买卖股票1:买卖一次。买的时候就只能是price[i]的最小值

  2. 买卖股票2:**买卖多次。**买可有上次卖出的利润来买dp[i-1] [1] - price[i].

    链接:买卖股票1、2

  3. 买卖股票3:**最多买卖两次。**买两个状态(第一次买,第二次买),卖两个状态(第二次卖,第二次卖),还可以拆分一个dp[0] [0]表示无状态

  4. 买卖股票4:最多买卖k次。由买卖3而来,通过3得到规律,并且使用dp[i] [j+1]表买,使用dp[i] [j+2]表示卖,循环的时候我们一组一组的进行循环(j+=2),找到每组买卖的最大利润

    链接:买卖股票3、4

  5. 买卖股票(含冷冻期):买卖多次,卖出有一天冷冻期。把卖出拆分为三个状态(之前卖出今日为卖出状态、今日卖出、昨日卖出今日冷冻期)

  6. 买卖股票(含手续费):买卖多次,卖出需手续费。就是在买卖2的基础上,卖的时候多扣一个手续费。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值