动态规划 —— LeetCode”股票买卖问题” 全家桶
本文主要是根据leetcode题解视频整理的一套解决 "股票买卖问题"的思路和代码,一起来看看吧!
本文会先根据这些股票问题整理出通用的 “状态转移方程”,然后针对每个问题作出相应的改变。
题目链接已贴出,输入输出示例等题目详情请看 原题链接!
代码部分(java)是我自己整理的,供大家参考!
(过OI的话直接copy对应方法体即可)
文章目录
“股票买卖问题”总体分析
限制条件
- 先买入才能卖出
- 不能同时参加多笔交易,再次买入时,需要先卖出
- 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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/
题目:
给定一个数组 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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/
题目:
给你一个整数数组 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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/
题目:
给定一个数组,它的第 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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/
题目:
给定一个整数数组 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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/
题目:
给定一个整数数组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)
原题链接:https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/
题目:
给定一个整数数组 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中的视频讲解:一种解法团灭买卖股票问题
文章粗浅,希望对大家有帮助!
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/159820.html