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];
}