"股票买卖问题"总体分析
限制条件
先买入才能卖出
不能同时参加多笔交易,再次买入时,需要先卖出
k >= 1才能进行交易,否则没有交易次数
定义操作
买入(buy)
卖出(sell)
不操作(沿用上一次的数据)
定义状态
i:天数
k:交易次数,每次交易包含买入和卖出,这里我们只在买入的时候需要将k - 1(这里解释一下:因为题目规定如果手中有股票是不能继续买入的,所以买入和卖出是一组连续的操作,我们只需要在其中一处-1即可。)
0:不持有股票
1:持有股票
举例
举例 dp[i][k][0]//第i天还可以交易k次手中没有股票 dp[i][k][1]//第i天还可以交易k次手中有股票 最终的最大收益是dp[n - 1][k][0]而不是dp[n - 1][k][1], 因为最后一天卖出肯定比持有收益更高
状态转移方程
//今天没有持有股票,分为两种情况: // 1. dp[i - 1][k][0],昨天没有持有,今天不操作。 // 2. dp[i - 1][k][1] + prices[i]昨天持有,今天卖出,今天手中就没有股票了。 dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i]) //今天持有股票,分为两种情况: // 1.dp [i - 1][k][1]昨天持有,今天不操作 // 2.dp[i - 1][k - 1][0] - prices [i]昨天没有持有,今天买入。 dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i]) //最大利润就是这俩种情况的最大值
下面这张图可以帮助理解状态转移方程的由来
题目
121. 买卖股票的最佳时机(easy)
题目:
给定一个数组 prices ,它的第i 个元素prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
分析
//第i天不持有由第i-1天不持有然后不操作和第i-1天持有然后卖出两种情况的最大值转移过来 dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i]) //第i天持有由第i-1天持有然后不操作和第i-1天不持有然后买入两种情况的最大值转移过来 dp[i][1][1] = Math.max( dp[i - 1][1][1],dp[i - 1][0][0] - prices[i]) // = Math.max(dp[i-1][1][1],-prices[i])// k=0时没有交易次数,dp[i-1][0][0] = 0 k是固定值1,不影响结果,所以可以不用管,简化之后如下 dp[i][0] = Math.max(dp[i - 1][0],dp[i - 1][1] + prices[i]) dp[i][1] = Math.max ( dp[i - 1][1],-prices [i])
code
public class dp_买卖股票的最佳时机121 { public static void main(String[] args) { int[] num = {7, 1, 5, 3, 6, 4}; // int[] num = {7, 6, 4, 3, 1}; System.out.println(maxProfit(num)); } /*//完整代码 public static int maxProfit(int[] prices) { int length = prices.length; int[][] dp = new int[length][2]; dp[0][0] = 0;//第0天不持有 dp[0][1] = -prices[0];//第0天持有 for (int i = 1; i < length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); } return dp[length - 1][0]; }*/ /*//状态压缩,利用滚动数组降维 public static int maxProfit(int[] prices) { int length = prices.length; int[] dp = new int[2]; dp[0] = 0;//第0天不持有 dp[0] = -prices[0];//第0天持有 for (int i = 1; i < length; i++) { dp[0] = Math.max(dp[0], dp[1] + prices[i]); dp[1] = Math.max(dp[1], -prices[i]); } return dp[0]; }*/ //语义化 public static int maxProfit(int[] prices) { int sell = 0;//出售 int buy = -prices[0];//买入 for (int i = 1; i < prices.length; i++) { sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, -prices[i]); } return sell; } }
这道题还有一个我自己实现的版本,
思路是:前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
public class dp_买卖股票的最佳时机121_self { // 动态规划 前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格} public static void main(String[] args) { int[] num = {7, 1, 5, 3, 6, 4}; // int[] num = {7, 6, 4, 3, 1}; System.out.println(maxProfit(num)); } public static int maxProfit(int[] prices) { int[] dp = new int[prices.length]; int min = prices[0]; for (int i = 1; i < prices.length; i++) { dp[i] = Math.max(dp[i - 1], prices[i] - min); min = Math.min(min, prices[i]); } return dp[dp.length - 1]; } }
122. 买卖股票的最佳时机 II(medium)
题目:
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润。
分析
//第i天不持有由第i-1天不持有然后不操作和第i-1天持有然后卖出两种情况的最大值转移过来 dp[i][k][0] = Math.max(dp[i - 1][k][0],dp[i - 1][k][1] + prices[i]) //第i天持有由第i-1天持有然后不操作和第i-1天不持有然后买入两种情况的最大值转移过来 dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i]) k同样不影响结果,简化之后如下 dp[i][0] = Math.max( dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = Math.max( dp[i - 1][1], dp[i - 1][0] - prices[i])
code
public class dp_买卖股票的最佳时机122 { public static void main(String[] args) { // int[] num = {7, 1, 5, 3, 6, 4}; int[] num = {1, 2, 3, 4, 5}; System.out.println(maxProfit(num)); } // 完整代码 /*public static int maxProfit(int[] prices) { int length = prices.length; int[][] dp = new int[length][2]; dp[0][0] = 0;//第0天不持有 dp[0][1] = -prices[0];//第0天买入 花了prices[0]元 for (int i = 1; i < length; i++) { dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); } return dp[length - 1][0]; }*/ // 状态压缩,利用滚动数组去掉一维 /*public static int maxProfit(int[] prices) { int length = prices.length; int[] dp = new int[2]; dp[0] = 0; dp[1] = -prices[0]; for (int i = 1; i < length; i++) { dp[0] = Math.max(dp[0], dp[1] + prices[i]); dp[1] = Math.max(dp[1], dp[0] - prices[i]); } return dp[0]; }*/ //语义化 public static int maxProfit(int[] prices) { int sell = 0; int buy = -prices[0]; for (int i = 1; i < prices.length; i++) { sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, sell - prices[i]); } return sell; } }
123. 买卖股票的最佳时机 III(hard)
题目:
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。分析
状态转移方程 dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i]) dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i]) k对结果有影响不能舍取,只能对k进行循环 for ( let i = 0; i < n; i++) { for (let k = maxK; k >= 1; k--) { dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i] ); dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 1][k - 1][0] - prices[i] ); } } // k=2,由于k很小,这里直接写出循环的结果来进一步优化 dp[i][2][0] = Math.max(dp[i - 1][2][0],dp[i - 1][2][1] + prices[i]) dp[i][2][1] = Math.max(dp[i - 1][2][1], dp[i - 1][1][0] - prices[i]) dp[i][1][0] = Math.max(dp[i - 1][1][0],dp[i - 1][1][1] + prices[i]) dp[i][1][1] = Math.max( dp[i - 1][1][1],dp[i - 1][0][0] - prices[i]) = Math.max(dp[i - 1][1][1],-prices[i])// k=0时没有交易次数,dp[i - 1][0][0] = 0 //去掉i这一维度 dp[2][0] = Math.max( dp[2][0], dp[2][1] + prices[i]) dp[2][1] = Math.max(dp [2] [1], dp[1][0] - prices[i]) dp[1][0] = Math.max( dp[1][0], dp[1][1] + prices [i]) dp[1][1] = Math.max( dp[1][1], dp[0][0] - prices [i] ) = Math.max(dp[1][1],-prices[i])// k=0时没有交易次数,dp[i - 1][0][0] = 0
code
public class dp_买卖股票的最佳时机123 { public static void main(String[] args) { int[] num = {3, 3, 5, 0, 0, 3, 1, 4}; // int[] num = {1, 2, 3, 4, 5}; System.out.println(maxProfit(num)); } //完整代码 + 降维 + 语义化 public static int maxProfit(int[] prices) { int length = prices.length; int sell1 = 0, sell2 = 0; int buy1 = -prices[0], buy2 = -prices[0]; for (int i = 1; i < length; i++) { sell2 = Math.max(sell2, buy2 + prices[i]); buy2 = Math.max(buy2, sell1 - prices[i]); sell1 = Math.max(sell1, buy1 + prices[i]); buy1 = Math.max(buy1, -prices[i]); } return sell2; } }
188. 买卖股票的最佳时机 IV(hard)
题目:
给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。分析
总体情况和122题类似,
122题可以交易无数次,188交易k次,所以直接在加一层k循环就可以
122最后的递推方程:
sell = Math.max( sell,buy + prices [i] );
buy = Math.max ( buy, -prices[i] );
code
public class dp_买卖股票的最佳时机188 { public static void main(String[] args) { int[] num = {2, 4, 1}; // int[] num = {3,2,6,5,0,3}; System.out.println(maxProfit(2, num)); } //完整代码 + 降维 + 语义化 public static int maxProfit(int k, int[] prices) { int n = prices.length; int[][] dp = new int[k + 1][2];//和123题—样求出所有k的状态 //初始化k次交易买入卖出的利润 for (int i = 0; i <= k; i++) { dp[i][0] = -prices[0];//buy 表示有股票 dp[i][1] = 0;//sell 表示没有股票 } for (int price : prices) { for (int j = 1; j <= k; j++) { //122题可以交易无数次,188交易k次,所以直接在加一层k循环就可以 //122最后的递推方程: //sell = Math.max( sell,buy + prices [i] ); //buy = Math.max ( buy, -prices[i] ); dp[j][1] = Math.max(dp[j][1], dp[j][0] + price); dp[j][0] = Math.max(dp[j][0], dp[j - 1][1] - price); } } return dp[k][1];//返回第k次清空手中的股票之后的最大利润 } }
309. 买卖股票的最佳时机含冷冻期(medium)
题目:
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
分析
状态转移方程 dp[i][k][0] = Math.max( dp[i - 1][k][0],dp[i - 1][k][1] + prices[i]) //冷却时间1天,所以要从i - 2天转移状态 //买入,卖出—---冷冻期—---买入,卖出 dp[i][k][1] = Math.max(dp[i - 1][k][1],dp[i - 2][k - 1][0] - prices[i]) 题目不限制k的大小,可以舍去 dp[i][0] = Math.max( dp[i - 1][0], dp[i - 1][1] + prices[i]) dp[i][1] = Math.max( dp[i - 1][1], dp[i - 2][0] - prices[i]) //降维i dp[0] = Math.max(dp[0], dp[1] + prices [i]) dp[1] = Math.max (dp[1], profit_freeze - prices [i])
code
public class dp_买卖股票的最佳时机含冷冻期309 { public static void main(String[] args) { // int[] num = {1, 2, 3, 0, 2};//3 int[] num = {1};//0 System.out.println(maxProfit(num)); } //完整代码 + 降维 + 语义化 public static int maxProfit(int[] prices) { int n = prices.length; int buy = -prices[0];//手中有股票 int sell = 0;//没有股票 int profit_freeze = 0; for (int i = 1; i < n; i++) { int temp = sell; sell = Math.max(sell, buy + prices[i]); buy = Math.max(buy, profit_freeze - prices[i]); profit_freeze = temp; } return sell; } }
714. 买卖股票的最佳时机含手续费(medium)
题目:
给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
分析
状态转移方程 //每次交易要支付手续费我们定义在卖出的时候扣手续费 dp[i][0] = max(dp[i - 1][0],dp[i - 1][1] + prices [i] - fee) dp[i] [1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
code
public class dp_买卖股票的最佳时机含手续费714 { public static void main(String[] args) { int fee = 2; // int fee = 3; int[] num = {1, 3, 2, 8, 4, 9};//8 // int[] num = {1,3,7,5,10,3};//6 System.out.println(maxProfit(num, fee)); } //完整代码 + 降维 + 语义化 public static int maxProfit(int[] prices, int fee) { int sell = 0; int buy = -prices[0]; for (int price : prices) { sell = Math.max(sell, buy + price - fee); buy = Math.max(buy, sell - price); } return sell; } }
总结
"股票买卖问题"是动态规划的一种,核心思想还是那句话:“当前的结果都是由之前的过程推导出来的” ,需要注意的是,股票问题属于一类问题,我们应对其进行一个总体的思考,根据限制条件和相应操作来定义状态,本题需要定义三个状态,但是具体到每道题的话,我们可以将不需要的状态去掉,并且可以通过滚动数组降维,从而进行状态压缩,最后通过适当的语义化来增强可读性。
参考leetcode中的视频讲解:一种解法团灭买卖股票问题
文章粗浅,希望对大家有帮助!