2023-01-11 23:02阅读: 109评论: 0推荐: 0

【动态规划】股票买卖问题

股票买卖问题

简介

在力扣上,股票买卖问题都可以通过动态规划求解,具体的题目如下:

序号 题目 区别
1 121. 买卖股票的最佳时机 只能交易一次
2 122. 买卖股票的最佳时机 II 交易次数不受限制
3 123. 买卖股票的最佳时机 III 交易次数受限制
4 188. 买卖股票的最佳时机 IV 交易次数为K(可能受限,也可能无限制)
5 309. 最佳买卖股票时机含冷冻期 交易次数不受限制,但包含冷冻期
6 714. 买卖股票的最佳时机含手续费 交易次数不受限制,但包含手续费

一种常用的方法是将「买入」和「卖出」分开进行考虑:「买入」为负收益,而「卖出」为正收益。在初入股市时,你只有「买入」的权利,只能获得负收益。而当你「买入」之后,你就有了「卖出」的权利,可以获得正收益。

显然,我们需要尽可能地降低负收益而提高正收益,因此我们的目标总是将收益值最大化。因此,我们可以使用动态规划的方法,维护在股市中每一天结束后可以获得的「累计最大收益」,并以此进行状态转移,得到最终的答案。

应用

应用1:Leetcode.121

题目

121. 买卖股票的最佳时机

分析

dp[i] 表示第 i 天能获取的最大利润。

边界条件

未开始交易的时候,收益为零,即

dp[0]=0

状态转移

显然,要获取最大的收益,我们首先想到的是在最低点买入,然后在最高点卖出,这样,我们就可以获取最大收益。

我们假设前 i1 天的股票的最低价格是 pricemin,由于,肯定要买入股票才会有收益,所以,在第 i 天,对于当前已经买入的股票,我们有两种选择:

  • 保留股票,收益与前一天的收益相同,即 dp[i1]

  • 卖出股票,收益就是当天的价格与历史最低价格之差,即 price[i]pricemin

所以,在第 i 天的收益,就是上述两种选择的最大值,那么状态转移方程就是:

dp[i]=max(dp[i1],prices[i]pricemin)

代码实现

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

应用2:Leetcode.122

题目

122. 买卖股票的最佳时机 II

分析

dp[i][0] 表示第 i 天不持有股票的最大利润,dp[i][1] 表示第 i 天持有股票的最大利润,设数组 prices 的长度为 n ,那么,在最后一天结束,手上不持有股票,就是最大收益,即 dp[n][0]

边界条件

显然,第零天不持有股票,最大收益为零,第零天持有股票,但是由于没有卖出,所以,最大收益为负无穷,即:

dp[0][0]=0dp[0][1]=

状态转移

那么,在第 i 天结束,存在两种情况:

  • 手上持有股票

    • 在第 i1 天,就已经持有有股票,第 i 天没有买入,收益与前一天的收益相同,即 dp[i1][1]

    • 在第 i1 天,未持有股票,在第 i 天买入股票,收益为第 i1 天的收益与第 i 天买入的成本之差,即 dp[i1][0]prices[i1]

    因此,在第 i 天结束后,若手上持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i1])

  • 手上不持有股票

    • 在第 i1 天手上就已经持有股票,在第 i 天结束卖出,那么收益为前一天的收益与当天卖出的股票价格之和,即 dp[i1][1]+prices[i1]

    • 在第 i1 天结束就已经卖出,第 i 没有进行交易,那么收益,与前一天的收益相同,即 dp[i1][0]

    因此,在第 i 天结束后,若手上不持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1])

所以,综上,状态转移方程为:

dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i1])dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1])

注意,第 i 天的股票价格是 prices[i1]

代码实现

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

应用3:Leetcode.123

题目

123. 买卖股票的最佳时机 III

分析

dp[i][k][0] 表示最大交易次数为 k ,第 i 天结束了,手上持不有股票的最大收益;dp[i][k][1] 表示最大交易次数为 k ,第 i 天结束了,手上持有股票的最大收益。

边界条件

显然,不管最大交易次数为多少次,第零天不持有股票,最大收益为零,第零天持有股票,但是由于没有卖出,所以,最大收益为负无穷,即:

dp[0][i][0]=0,0iKdp[0][i][1]=,0iK

状态转移

那么,假设最大交易次数限制k买入的限制),在第 i 天结束,存在两种情况:

  • 手上持有股票

    • 在第 i1 天,就已经持有有股票,第 i 天没有买入,收益与前一天的收益相同,即 dp[i1][k][1]

    • 在第 i1 天,未持有股票,在第 i 天买入股票,收益为第 i1 天的收益与第 i 天买入的成本之差,即 dp[i1][k1][0]prices[i1]

    因此,在第 i 天结束后,若手上持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][k][1]=max(dp[i1][k][1],dp[i1][k1][0]prices[i1])

  • 手上不持有股票

    • 在第 i1 天手上就已经持有股票,在第 i 天结束卖出,那么收益为前一天的收益与当天卖出的股票价格之和,也就是说,在第 i1天结束前,就已经进行了第 k 次买入,最后一天将股票卖出,完成第 k 次交易,即 dp[i1][k][1]+prices[i1]

    • 在第 i1 天结束就已经卖出,第 i 没有进行交易,那么收益,与前一天的收益相同,也就是说,前一天就已经完成了 k 次交易,即 dp[i1][k][0]

    因此,在第 i 天结束后,若手上不持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][k][0]=max(dp[i1][k][0],dp[i1][k][1]+prices[i1])

所以,综上,状态转移方程为:

dp[i][k][0]=max(dp[i1][k][0], dp[i1][k][1]+prices[i1]),1in, 1kKdp[i][k][1]=max(dp[i1][k][1], dp[i1][k1][0]prices[i1]),1in, 1kK

对于本题,我们令 k=2 即可,并依次枚举 k=1,2 的场景即可。

代码实现

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

应用4:Leetcode.188

题目

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

分析

假设数组 prices 的长度为 n ,即天数为 n ,容易看出,一次交易包含买入和卖出,至少需要两天,也就是说,有效的限制 k 需要满足 kn2。否则,就没有约束作用了,相当于没有限制了。

所以,按照 K 是否有效两种情况,这里直接套用前面的模板即可。

代码实现

class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if k == 0:
return 0
if k > len(prices) // 2:
return self.maxProfit1(prices)
else:
return self.maxProfit2(k, prices)
def maxProfit1(self, prices: List[int]) -> int:
n = len(prices)
dp = [[0 for _ in range(2)] for _ in range(n + 1)]
dp[0][0] = 0
dp[0][1] = -float("inf")
for i in range(1, n + 1):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1])
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i - 1])
return dp[-1][0]
def maxProfit2(self, K: int, prices: List[int]) -> int:
n = len(prices)
dp = [[[0 for _ in range(2)] for _ in range(K + 1)] for _ in range(n + 1)]
for _k in range(1, K + 1):
dp[0][_k][0] = 0
dp[0][_k][1] = -float("inf")
for i in range(1, n + 1):
for _k in range(K, 0, -1):
dp[i][_k][0] = max(dp[i - 1][_k][0], dp[i - 1][_k][1] + prices[i - 1])
dp[i][_k][1] = max(dp[i - 1][_k][1], dp[i - 1][_k - 1][0] - prices[i - 1])
return dp[-1][K][0]

应用5:Leetcode.309

题目

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

分析

dp[i][0] 表示第 i 天不持有股票的最大利润,dp[i][1] 表示第 i 天持有股票的最大利润,设数组 prices 的长度为 n ,那么,在最后一天结束,手上不持有股票,就是最大收益,即 dp[n][0]

注意,这里的「处于冷冻期」指的是在第 i 天结束之后的状态。也就是说:如果第 i 天结束之后处于冷冻期,那么第 i+1 天无法买入股票。

边界条件

显然,第零天不持有股票,最大收益为零,第零天持有股票,但是由于没有卖出,所以,最大收益为负无穷,即:

dp[0][0]=0dp[0][1]=

状态转移

由于股票买卖需要间隔一天作为冷冻期,所以从卖出的状态转移时,中间要间隔一天。

那么,在第 i 天结束,存在两种情况:

  • 手上持有股票

    • 在第 i1 天,就已经持有有股票,第 i 天没有买入,收益与前一天的收益相同,即 dp[i1][1]
    • 在第 i2 天,不持有股票,在第 i 天买入股票,收益为第 i2 天的收益与第 i 天买入的成本之差,即 dp[i2][0]prices[i1]
      因为,如果要第 i 天买入股票,那么,我们就要保证第 i1 天手上不持有股票,且不处于冷冻期,因此,必须要保证第 i2 天,就不持有股票。

    综上,在第 i 天结束后,若手上持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][1]=max(dp[i1][1],dp[i2][0]prices[i1])

  • 手上不持有股票

    • 在第 i1 天手上就已经持有股票,在第 i 天结束卖出,那么收益为第 i1 天的收益与当天卖出的股票价格之和,即 dp[i1][1]+prices[i1]
    • 在第 i1 天结束就已经卖出,第 i 没有进行交易,那么收益,与第 i1 天的收益相同,即 dp[i1][0]

    综上,在第 i 天结束后,若手上不持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1])

所以,综上,状态转移方程为:

dp[i][1]=max(dp[i1][1],dp[i2][0]prices[i1])dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1])

代码实现

from typing import List
class Solution:
def maxProfit(self, prices: List[int]) -> int:
n = len(prices)
dp = [[0 for _ in range(2)] for i in range(n + 1)]
dp[0][0] = 0
dp[0][1] = -float("inf")
for i in range(1, n + 1):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1])
dp[i][1] = max(dp[i - 1][1], dp[i - 2][0] - prices[i - 1])
return dp[n][0]

应用6:Leetcode.714

题目

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

分析

dp[i][0] 表示第 i 天不持有股票的最大利润,dp[i][1] 表示第 i 天持有股票的最大利润,设数组 prices 的长度为 n ,那么,在最后一天结束,手上不持有股票,就是最大收益,即 dp[n][0]

边界条件

显然,第零天不持有股票,最大收益为零,第零天持有股票,但是由于没有卖出,所以,最大收益为负无穷,即:

dp[0][0]=0dp[0][1]=

状态转移

那么,在第 i 天结束,存在两种情况:

  • 手上持有股票

    • 在第 i1 天,就已经持有有股票,第 i 天没有买入,收益与前一天的收益相同,即 dp[i1][1]
    • 在第 i1 天,不持有股票,在第 i 天买入股票,收益为第 i1 天的收益与第 i 天买入的成本之差,即 dp[i1][0]prices[i1]

    综上,在第 i 天结束后,若手上持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i1])

  • 手上不持有股票

    • 在第 i1 天,就已经持有股票,第 i 天结束卖出,那么收益为第 i1 天的收益与当天卖出的股票收益之和,即 dp[i1][1]+prices[i1]fee
      注意,卖出股票的收益等于卖出时股票的价格减去手续费。
    • 在第 i1 天,不持有股票,第 i 没有进行交易,那么收益,与第 i1 天的收益相同,即 dp[i1][0]

    综上,在第 i 天结束后,若手上不持有股票,那么最大收益,就是上述两种情况的最大值,所以,状态转移方程为:

    dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1]fee)

所以,综上,状态转移方程为:

dp[i][0]=max(dp[i1][0],dp[i1][1]+prices[i1])dp[i][1]=max(dp[i1][1],dp[i1][0]prices[i1]fee)

代码实现

class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp = [[0 for _ in range(2)] for i in range(n + 1)]
dp[0][0] = 0
dp[0][1] = -float("inf")
for i in range(1, n + 1):
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1]- fee)
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i - 1])
return dp[-1][0]

参考:

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17041468.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(109)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.