动态规划中的股票问题全解析

股票问题

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

股票问题为什么使用动态规划?
因为求得某一天的最大利润,一定和前一天的最大利润有关,因为股票是按照天数连续增长的。针对每一天,前一天的情况都在考虑范围内。

二维dp-常规股票问题

121. 买卖股票的最佳时机

使用五步法分析:

  1. 确定dp数组以及下标含义:dp[i][0]表示第i天持有股票所得最多现金。dp[i][1]表示第i天不持有股票所得最多现金。
  2. 以上两种情况,每一种情况都有两个状态推导。
    dp[i][0]可以由两个状态推导而来:
    a. 第i - 1天就持有股票,保持现状,所得金额为:dp[i - 1][0]。
    b. 第i天才买入股票,所得金额为:-price[i]。
    dp[i][1]也可以由两个状态推导而来:
    a. 第i - 1天不持有股票,保持现状,所得金额为:dp[i - 1][1]。
    b. 第i天卖出股票,所得金额为:dp[i - 1][0] + prices[i]。

3、 dp数组初始化:dp[0][0]为第0天持有股票,dp[0][0] -= prices[i]。dp[0][1]表示第0天不持有股票,dp[0][1] = 0。

4、 遍历顺序:从前往后。

5、 举例推导dp。

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int [][] dp = new int [n][2];
        //dp[i][0]:表示第i天不持有时的最大利润
        //dp[i][1]:表示第i天持有时的最大利润
        dp[0][0] = 0;
        dp[0][1] = - prices[0];
        //举例每种情况
        for(int i = 1 ; i < n ; i ++){
            //不持有的两种状态推导
            //1. 今天刚卖出去的。dp[i][0] = dp[i - 1][1] + price[i]
            //2. 之前卖出去的。 dp[i][0] = dp[i - 1][0]
            dp[i][0] = Math.max(dp[i - 1][1] + prices[i] , dp[i - 1][0]);
            //持有的两种状态推导
            //1. 今天刚持有的。dp[i][1] = - price[i]
            //2. 之前就持有的。 dp[i][1] = dp[i - 1][1]
            dp[i][1] = Math.max(-prices[i] , dp[i - 1][1]);
        }
        return dp[n - 1][0];
    }
}

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

现在变换问题:股票可以买卖多次,该怎么算?
本题的股票可以买卖多次,当第i天买入股票的时候,所得现金可能有之前买过的利润。
递推公式:dp[i][0]表示第i天持有股票的最多现金。
● 第i- 1天持有股票,dp[i - 1][0]。
● 第i天买入股票,(可能包含之前的利润)dp[i - 1][1] - prices[i]
dp[i][1]表示第i天不持有股票的最多现金。
● 第i天不持有股票,dp[i - 1][1]。
● 第i天卖出股票prices[i] + dp[i - 1][0]

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int [][] dp = new int [n][2];
        dp[0][0] = 0;
        dp[0][1] = - prices[0];
        for(int i = 1 ; i < n ; i ++){
            dp[i][0] = Math.max(dp[i - 1][1] + prices[i] , dp[i - 1][0]);
            //唯一变化:持有的两种状态推导
            //1. 今天刚持有的。dp[i][1] =dp[i - 1][0] - price[i]
            dp[i][1] = Math.max(dp[i - 1][0]-prices[i] , dp[i - 1][1]);
        }
        return dp[n - 1][0];
    }
}

714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)

可以无限次买卖股票,但是包含手续费?
相对于动态规划:122.买卖股票的最佳时机II (opens new window),本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int n = prices.length;
        int [][] dp= new int [n + 1][2];
        dp[0][1] = -prices[0];
        dp[0][0] = 0;
        for(int i = 1 ; i < n ; i ++){
            dp[i][0] = Math.max(dp[i - 1][0] , prices[i] + dp[i - 1][1] - fee);
            dp[i][1] = Math.max(dp[i - 1][1] , dp[i - 1][0] - prices[i]);
        }
        return dp[n - 1][0];
    }
}

多维dp-其他股票问题

123. 买卖股票的最佳时机 III - 力扣(LeetCode)

a. 确定dp数组下标及其含义:
dp[i][j]中i表示第i天,j为[0 - 4]五个状态,dp[i][j]为第i天状态j所剩的最大现金。
五个状态为:
ⅰ. 没有操作 (其实我们也可以不设置这个状态)
ⅱ. 第一次持有股票
ⅲ. 第一次不持有股票
ⅳ. 第二次持有股票
ⅴ. 第二次不持有股票

b. 确定递推公式:
达到dp[i][1]状态,有两个具体操作:
■ 操作一:第i天买入股票了,那么dp[i][1] = dp[i-1][0] - prices[i]
■ 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

一定是选最大的,所以 dp[i][1] = max(dp[i-1][0] - prices[i], dp[i - 1][1]);同理可推出剩下状态部分:
dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);

c. dp数组如何初始化
第0天没有操作,dp[0][0] = 0;
第0天做第一次买入的操作,dp[0][1] = -prices[0];
第0天做第一次卖出的操作,(当天买入当天卖出)dp[0][2] = 0;
第0天做第二次买入的操作,dp[0][3] = - prices[0];
第0天做第二次卖出的操作,dp[0][4] = 0。

d. 举例推导dp数组。
最后返回最后一次不持有的dp数组,因为如果第一次卖出已经是最大值了,那么我们可以在当天立刻买入再立刻卖出。所以dp[4][4]已经包含了dp[4][2]的情况。也就是说第二次卖出手里所剩的钱一定是最多的。

class Solution {
    public int maxProfit(int[] prices) {
        int len = prices.length;
        // 0:不操作
        // 1:第一次持有
        // 2:第一次不持有
        // 3:第二次持有
        // 4:第二次不持有
        int [][] dp = new int [len][5];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[0][2] = 0;
        dp[0][3] = -prices[0];
        dp[0][4] = 0;
        for(int i = 1 ; i < len ; i ++){
            // 不操作
            dp[i][0] = dp[i - 1][0];
            // 第一次持有 
            dp[i][1] = Math.max(dp[i - 1][1] ,dp[i - 1][0] - prices[i]);
            // 第一次不持有
            dp[i][2] = Math.max(dp[i - 1][2] , dp[i - 1][1] + prices[i]);
            // 第二次持有
            dp[i][3] = Math.max(dp[i - 1][3] , dp[i - 1][2] - prices[i]);
            // 第二次不持有
            dp[i][4] = Math.max(dp[i - 1][4] , dp[i - 1][3] + prices[i]);
        }
        return dp[len - 1][4];
    }
}

188. 买卖股票的最佳时机 IV

最多可以完成k笔交易,该怎么算?
与最多完成两次交易类似,我们先找到完成两次交易的规律:
发现:2次交易我们确定了五种状态,那么k次交易,我们可以确定2 * k + 1次状态。

  1. dp数组含义:
    dp[i][0]:第i天不做操作的最大金额。
    dp[i][1]:第i天持有股票的最大金额。
    dp[i][2]:第i天不持有股票的最大金额。
    ······
    规律:j为奇数表示持有,j为偶数表示不持有。
  2. 递推公式:
    dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
    dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])
    规律:j为偶数时:dp[i][j] = max(dp[i - 1][j - 1] + prices[i] , dp[i - 1][j])。
    j为奇数时:dp[i][j] = max(dp[i - 1][j - 1] - prices[i] , dp[i - 1][j])
  3. dp数组初始化:
    规律:j为奇数时,初始化为 - prices[0]。
  4. 遍历顺序:顺序遍历
  5. 举例推导。
class Solution {
    public int maxProfit(int k, int[] prices) {
        // 按照2次的规律来写
        // 买入都是单数 卖出都是双数
        int len = prices.length;
        // 2次dp[5]
        int [][] dp = new int [len][2 * k + 1];
        // 初始化dp
        for(int j = 1 ; j <= 2 * k ; j +=2){
            dp[0][j] = -prices[0];
        }
        for(int i = 1 ; i < len ; i ++){
            for(int j = 0 ; j <= 2 * k ; j ++){
                if(j == 0) dp[i][0] = dp[i - 1][0];
                    // 全部是持有状态 之前就持有了 || 之前没持有,今天才持有
                else if(j % 2 != 0) dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] - prices[i]);
                else if(j % 2 == 0) dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - 1] + prices[i]);
            }
        }
        // 不持有状态
        return dp[len - 1][2 * k];
    }
}

309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)

可以买卖多次,但是股票包含冷冻期,该怎么算?
最开始可以找到三个状态:卖出(不包含冷冻期)、买入、冷冻期。

// 状态流转
// 卖出, 今天卖出 || 之前卖出 || 之前是冷冻期
dp[i][0] = Math.max(dp[i - 1][2] + prices[i],Math.max(dp[i - 1][0],dp[i - 1][1]));
// 今天是冷冻期 前一天卖出了
dp[i][1] = dp[i - 1][0];
// 今天买入 前一天就买入了 冷冻期买入了 
dp[i][2] = Math.max(dp[i - 1][2],dp[i - 1][1] - prices[i]);

也可以考虑把今天卖出状态单独列出:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int [][] dp = new int [n][4];
        //卖出状态
        dp[0][0] = 0;
        //持有状态 
        dp[0][1] = - prices[0];
        //今天卖出状态
        dp[0][2] = 0;
        //今天为冷冻期
        dp[0][3] = 0;

        for(int i = 1 ; i < n ; i++){
            //流转到买入
            dp[i][1] = Math.max(Math.max(dp[i - 1][1] , dp[i - 1][0] - prices[i]) , dp[i - 1][3] - prices[i]);
            //流转到卖出
            dp[i][0] = Math.max(dp[i - 1][0] , dp[i - 1][3]);
            //流转到今天卖出
            dp[i][2] = dp[i - 1][1] + prices[i];
            //流转到冷冻
            dp[i][3] = dp[i - 1][2];
        }
        return Math.max(Math.max(dp[n - 1][0] , dp[n - 1][2]) , dp[n - 1][3]);
    }
}
  • 11
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sivan_Xin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值