016、股票买卖问题(labuladong)

Leetcode股票买卖问题

基于labuladong的算法网站,自己总结思考内容和代码的,网址链接点我

1、概述

关于这个问题的技巧为:状态机,实际上是一个DP table

题目如下:
买彩票的问题

后续的算法框架是根据这张图上的内容作为条件,进行推导的

2、算法框架

(1)基本介绍

动态规划最重要的3个元素:

  • base case
  • 状态
  • 选择

动态规划的代码框架:

for 状态1 in 状态1的所有取值:
   for 状态2 in 状态2的所有取值:
       for ...
           dp[状态1][状态2][...] = 择优(选择1,选择2...)

两个核心要点:

  • 状态的穷举
  • 状态的选择

(2)穷举框架

状态:

  • 当前是第几天:n
  • 当前我还可以完成多少笔交易:k
  • 当前我手中是否持有这个股票:0(未持有)、1(持有)

选择:

  • 买:buy
  • 卖:sell
  • 不操作:reset

注意:

  • 买是基于当前我手中不持有该股票,并且我还可以完成的交易笔数大于0
  • 卖是基于当前我手中持有该股票
  • DP table是根据状态进行设定的,有三个影响状态的变量,故DP table为三维数组
  • 函数是根据选择进行设定的,有三个选择,故函数参数为三个

(3)状态转移框架

状态转移的意思是,目前这种情况得到的结果是基于上次状态的做出什么选择得到的

dp[n][k][0];

含义如下: 
- 该dp的意思是:第n天,交易次数为k次,当前没持有该股票时候的最大利润
- 那么我们需要考虑的是第n-1天时,发生了什么操作得到了该dp,有2种情况:
- * dp[n-1][k-1][1],第n-1天时,交易次数为k-1次,持有该股票,但在第n天时候卖了
- * dp[n-1][k][0],第n-1天时,交易次数为k次,未持有该股票,在第n天无操作
dp[n][k][1];

含义如下:
- 该dp的意思是,第n天,交易次数为k次,持有该股票时候的最大利润
- 同样只需要考虑第n-1天,基于什么情况做了什么操作得到该dp,有2种情况:
- * dp[n-1][k-1][0],第n-1天时,操作次数为k-1次,未持有该股票,但在第n天购买了该股票
- * dp[n-1][k][1],第n-1天时,操作次数为k次,持有该股票,但在第n天不进行任何操作

完成上述的状态转移后,现在开始考虑base case,如下:

  • dp[-1][…][0]=0:i从0开始,当i=-1的时候意味着该彩票还没有价格
  • dp[-1][…][1]=最小值:彩票还没有价格的时候是不可能持有彩票的
  • dp[…][0][0]=0:剩下的交易次数为0,没持有该股票,这时候的利润为0
  • dp[…][0][1]=最小值:不允许交易的情况下是不可能持有股票的

注意点:

  • 数组索引-1怎么表示
  • 最小值怎么表示

状态转移框架如下:

// base case
dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][0][1] = -最小值

// 状态转移方程
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

3、买卖股票的最佳时机

力扣第121题,买卖股票的最佳时机

[121]买卖股票的最佳时机

class Solution {
    public int maxProfit(int[] prices) {
        // 状态变量为:天数,剩下的操作次数,是否持有该股票
        int[][] memo = new int[prices.length][2];// dp table
        return dp(prices, memo);
    }

    // 该函数为返回在股票的最后一天时,获取的最大利润
    int dp(int[] prices, int[][] memo) {
        int days = prices.length;
        // 状态的可能性
        for (int i = 0; i < days; i++) {
            // base case
            if (i == 0) {
                memo[i][0] = 0;
                memo[i][1] = -prices[i];
                continue;
            }
            // 状态转移(可操作次数为1次,只能买一次和卖一次)
            // 当第i天,没持有该股票意味着要么第i天卖了,要么就是昨天就未持有该股票
            memo[i][0] = Math.max(memo[i - 1][1] + prices[i], memo[i - 1][0]);
            // 当第i天持有该股票,要么当天刚买的,要么昨天就持有
            memo[i][1] = Math.max(-prices[i], memo[i - 1][1]);
        }
        return memo[days - 1][0];
    }
}

4、买卖股票的最佳时机Ⅱ

力扣第122题,买卖股票的最佳时机Ⅱ

[122]买卖股票的最佳时机 II

class Solution {
    public int maxProfit(int[] prices) {
        int days = prices.length;
        int[][] memo = new int[days][2];// dp table
        return dp(prices, memo);
    }

    // 返回的结果为,最后一天出售股票获取的最大利润
    int dp(int[] prices, int[][] memo) {
        int days = prices.length;
        for (int i = 0; i < days; i++) {
            // base case
            if (i == 0) {
                memo[0][0] = 0;
                memo[0][1] = -prices[i];
                continue;
            }
            // 状态转移(可操作次数不限制)
            // 昨天就是未持有该股票,今天也没进行任何操作;昨天持有股票,但今天卖了
            memo[i][0] = Math.max(memo[i - 1][0], memo[i - 1][1] + prices[i]);
            // 昨天就持有该股票,今天没进行任何操作;昨天未持有该股票,今天买了这个股票
            memo[i][1] = Math.max(memo[i - 1][1], memo[i - 1][0] - prices[i]);
        }
        return memo[days - 1][0];
    }
}

5、最佳买卖股票时机含冷冻期

力扣第309题,最佳买卖股票时机含冷冻期

[309]最佳买卖股票时机含冷冻期

class Solution {
    public int maxProfit(int[] prices) {
        int[][] memo = new int[prices.length][2];
        return dp(prices, memo);
    }

    // 返回最后一天操作股票后得到的最大利润
    int dp(int[] prices, int[][] memo) {
        int days = prices.length;
        for (int i = 0; i < days; i++) {
            // base case
            if (i == 0) {
                memo[0][0] = 0;
                memo[0][1] = -prices[i];
                continue;
            }
            // base case
            if (i == 1) {
                // 昨天也未持有股票;昨天持有股票但是今天卖了
                memo[1][0] = Math.max(memo[0][0], memo[0][1] + prices[1]);
                // 昨天持有但今天未操作;昨天未持有但今天买了
                memo[1][1] = Math.max(memo[0][1], memo[0][0] - prices[1]);
                continue;
            }

            // 状态转移
            // 昨天就没持有该股票;昨天持有该股票,今天卖了
            memo[i][0] = Math.max(memo[i - 1][0], memo[i - 1][1] + prices[i]);
            // 昨天就持有该股票;过了冷冻期后买的股票
            memo[i][1] = Math.max(memo[i - 1][1], memo[i - 2][0] - prices[i]);
        }
        return memo[days - 1][0];
    }
}

6、买卖股票的最佳时机含手续费

力扣第714题,买卖股票的最佳时机含手续费

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

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int[][] memo = new int[prices.length][2];
        return dp(prices, fee, memo);
    }

    int dp(int[] prices, int fee, int[][] memo) {
        int days = prices.length;
        for (int i = 0; i < days; i++) {
            // base case
            if (i == 0) {
                memo[i][0] = 0;
                memo[i][1] = -prices[i];
                continue;
            }

            // 卖股票的时候才需要付手续费
            // 昨天未持有股票且今天无操作;昨天持有股票但今天卖了
            memo[i][0] = Math.max(memo[i - 1][0], memo[i - 1][1] + prices[i] - fee);
            // 昨天持有股票且今天无操作;昨天未持有股票但今天买了
            memo[i][1] = Math.max(memo[i - 1][1], memo[i - 1][0] - prices[i]);
        }
        return memo[days - 1][0];
    }
}

7、买卖股票的最佳时机Ⅲ

力扣第123题,买卖股票的最佳时机Ⅲ

[123]买卖股票的最佳时机 III

class Solution {
    public int maxProfit(int[] prices) {
        int[][][] memo = new int[prices.length][3][2];
        return dp(prices, memo);
    }

    // 最多完成两次交易条件下,最后一天的最大利润
    // 每次buy股票算一次操作
    int dp(int[] prices, int[][][] memo) {
        int days = prices.length;
        for (int i = 0; i < days; i++) {
            // 交易次数
            for (int j = 2; j > 0; j--) {
                // base case
                if (i == 0) {
                    memo[i][j][0] = 0;
                    memo[i][j][1] = -prices[i];
                    continue;
                }

                // 昨天未持有股票且今天无操作;昨天有股票但是今天sell出去了
                memo[i][j][0] = Math.max(memo[i - 1][j][0], memo[i - 1][j][1] + prices[i]);
                // 昨天未持有股票但今天buy了;昨天持有股票今天无操作
                memo[i][j][1] = Math.max(memo[i - 1][j - 1][0] - prices[i], memo[i - 1][j][1]);
            }

        }

        return memo[days - 1][2][0];
    }
}

8、买卖股票的最佳时机Ⅳ

力扣第188题,买卖股票的最佳时机Ⅳ

[188]买卖股票的最佳时机 IV

class Solution {
    public int maxProfit(int k, int[] prices) {
        if (k == 0 || prices.length == 0) {
            return 0;
        }
        int[][][] memo = new int[prices.length][k + 1][2];// dp table
        return dp(prices, k, memo);
    }

    // 返回最后一天,操作次数小于等于k的情况下,能获取的最大利润
    int dp(int[] prices, int k, int[][][] memo) {
        int days = prices.length;
        for (int i = 0; i < days; i++) {
            for (int j = k; j > 0; j--) {
                // memo[i][j][0]:第i天,最大操作次数j,未持有股票;最大利润
                // memo[i][j][1]:第i天,最大操作次数j,持有股票;最大利润

                // base case
                if (i == 0) {
                    memo[i][j][0] = 0;
                    memo[i][j][1] = -prices[i];
                    continue;
                }
                // 状态转移(buy就算操作一次)
                // 昨天没持有股票且今天无操作;昨天持有股票但今天sell了
                memo[i][j][0] = Math.max(memo[i - 1][j][0], memo[i - 1][j][1] + prices[i]);
                // 昨天持有且今天无操作;昨天未持有但今天buy了
                memo[i][j][1] = Math.max(memo[i - 1][j][1], memo[i - 1][j - 1][0] - prices[i]);
            }
        }
        return memo[days - 1][k][0];
    }

}

9、玩法归一

最后实现一个函数,考虑上述买彩票的所有变量:

int maxProfit_all_in_one(int k, int[] prices, int time, int fee)

- k:最多的可操作次数
- prices[i]:第i天股票的价值
- time:操作冷静期
- fee:每次操作的费用
- 返回考虑这些变量时,我们可以从股票中得到的最大利润

代码如下:

    int maxProfit_all_in_one(int k, int[] prices, int time, int fee) {
        int days = prices.length;// 天数
        int[][][] memo = new int[days][k + 1][2];// dp table
        // 天数
        for (int i = 0; i < days; i++) {
            // 最大的可操作次数
            for (int j = k; j > 0; j--) {
                // base case
                if (i == 0) {
                    memo[i][j][0] = 0;
                    memo[i][j][1] = -prices[i];
                    continue;
                }
                // 考虑冷冻期time的base case
                if (i - time <= 0) {
                    memo[i][j][0] = Math.max(memo[i - 1][j][0], memo[i - 1][j][1] + prices[i]);
                    memo[i][j][1] = Math.max(memo[i - 1][j][1], -prices[i] - fee);
                    continue;
                }
                // 状态转移(buy算操作次数,buy算fee) 
                // 昨天没有股票且今天没操作;昨天有股票但今天sell了
                memo[i][j][0] = Math.max(memo[i - 1][j][0], memo[i - 1][j][1] + prices[i]);
                // 昨天有股票且今天无操作;昨天未持有股票但今天buy了
                memo[i][j][1] = Math.max(memo[i - 1][j][1], memo[i - time][j - 1][0] - prices[i] - fee);
            }
        }
        return memo[days - 1][k][0];
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值