首页 > 其他分享 >【动态规划】股票买卖问题

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

时间:2023-01-11 15:36:23浏览次数:60  
标签:股票买卖 max 收益 range 股票 prices 动态 规划 dp

目录

股票买卖问题

简介

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

序号 题目 区别
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 \]

状态转移

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

我们假设前 \(i-1\) 天的股票的最低价格是 \(price_{min}\),由于,肯定要买入股票才会有收益,所以,对于第 \(i\) 天当前已经买入的股票,我们有两种选择:

  • 保留股票,收益与前一天的收益相同,即 \(dp[i - 1]\);
  • 卖出股票,收益就是当天的价格与历史最低价格之差,即 $price[i] - price_{min} $;

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

\[dp[i] = \max(dp[i-1], prices[i] - price_{min}) \]

代码实现

from typing import List

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        dp = [0] * n
        dp[0] = 0
        min_price = prices[0]
        max_profit = dp[0]
        for i in range(1, n):
            min_price = min(min_price, prices[i])
            dp[i] = max(dp[i - 1], prices[i] - min_price)
            max_profit = max(max_profit, dp[i])

        return max_profit

应用2:Leetcode.122

题目

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

分析

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

边界条件

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

\[\begin{aligned} & dp[0][0] = 0 \\ & dp[0][1] = -\infty \end{aligned} \]

状态转移

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

  • 手上持有股票

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

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

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

  • 手上不持有股票

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

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

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

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

\[\begin{aligned} dp[i][1] = \max(dp[i-1][1], dp[i-1][0] - prices[i-1])\\ dp[i][0] = \max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1]) \end{aligned} \]

注意,第 \(i\) 天的股票价格是 \(prices[i - 1]\) 。

代码实现

from typing import List

class Solution:
    def maxProfit(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[n][0]

应用3:Leetcode.123

题目

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

分析

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

边界条件

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

\[\begin{aligned} & dp[0][k][0] = 0 \\ & dp[0][k][1] = -\infty \end{aligned} \]

状态转移

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

  • 手上持有股票

    • 在第 \(i-1\) 天,就已经持有有股票,第 \(i\) 天没有买入,收益与前一天的收益相同,即 \(dp[i - 1][k][1]\);
    • 在第 \(i-1\) 天,未持有股票,在第 \(i\) 天买入股票,收益为第 \(i-1\) 天的收益与第 \(i\) 天买入的成本之差,即 \(dp[i-1][k - 1][0] - prices[i - 1]\) 。

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

    \[dp[i][1] = \max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i-1]) \]

  • 手上不持有股票

    • 在第 \(i - 1\) 天手上就已经持有股票,在第 \(i\) 天结束卖出,那么收益为前一天的收益与当天卖出的股票价格之和,也就是说,前一天已经完成了 \(k-1\) 次交易,最后一天完成 \(1\) 次交易,即 \(dp[i - 1][k - 1][1] + prices[i - 1]\);
    • 在第 \(i - 1\) 天结束就已经卖出,第 \(i\) 没有进行交易,那么收益,与前一天的收益相同,也就是说,前一天就已经完成了 \(k\) 次交易,即 \(dp[i - 1][k][0]\) 。

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

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

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

\[\begin{aligned} &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]) \end{aligned} \]

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

代码实现

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        K = 2
        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, len(n + 1):
            for k in range(1, K + 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[n][K][0]

应用4:Leetcode.188

题目

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

分析

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

所以,按照 \(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:
        """ 最多交易K次的最大收益
        """
        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(1, K + 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\) 天无法买入股票。

边界条件

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

\[\begin{aligned} & dp[0][0] = 0 \\ & dp[0][1] = -\infty \end{aligned} \]

状态转移

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

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

  • 手上持有股票

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

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

    \[dp[i][1] = \max(dp[i - 1][1], dp[i - 2][0] - prices[i - 1]) \]

  • 手上不持有股票

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

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

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

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

\[\begin{aligned} dp[i][1] = \max(dp[i - 1][1], dp[i - 2][0] - prices[i - 1])\\ dp[i][0] = \max(dp[i - 1][0], dp[i - 1][1] + prices[i - 1]) \end{aligned} \]

代码实现

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]\) 。

边界条件

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

\[\begin{aligned} & dp[0][0] = 0 \\ & dp[0][1] = -\infty \end{aligned} \]

状态转移

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

  • 手上持有股票

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

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

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

  • 手上不持有股票

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

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

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

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

\[\begin{aligned} & 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] - fee) \end{aligned} \]

代码实现

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]

参考:

标签:股票买卖,max,收益,range,股票,prices,动态,规划,dp
From: https://www.cnblogs.com/larry1024/p/17041468.html

相关文章

  • 在模型内动态生成按钮
    @api.modeldeffields_view_get(self,view_id=None,view_type='form',toolbar=False,submenu=False):"""Changestheviewdynamically......
  • 【做题笔记】动态规划专题(DP)
    这里记录笔者这几天做的有关于dp的题目。树形dp#1P1122最大子树和题目链接:https://www.luogu.com.cn/problem/P1122题意:选出一个联通分量,使得联通分量的点的点权......
  • android jni静态注册和动态注册
    静态注册对于静态注册的jni函数而言,jni函数签名名称要与java层对应的函数名称一一对应。当一个java类被加载时会调用LoadMethod将其所有的方法也都加载到虚拟机中,并调用Li......
  • 误删动态库的自救引导
    若是误删或误移动态库/lib64,则会导致系统绝大多数依赖动态库的可执行程序无法执行,例如cp\ls\df\mkdir\cat\mv....../ld-linux-x86-64.so.2是动态库加载器,首先查找可执行文......
  • TypeScript 2.0 与 AngularJS 2.0 的最新动态
    Lightbot微软终于发布了TypeScript2.0的第一个RC版本。TypeScript是一个简化版的JavaScript语言,被大量用于各种Web项目,包括下面提到的著名的AngularJS框架......
  • js动态生成唯一id
    一.引入时间戳,生成可控长度的随机数随机数长度控制,定义一个长度变量(length),生成可控长度的随机数:Math.random().toString(36).substr(3,length)引入时间戳:Date.now()......
  • Windows Visual Studio中静态库与动态库加载
    VS项目中的包含目录、库目录、附加包含目录、附加库目录、附加依赖项在项目->属性->配置属性下进行配置,具体说明如下:1静态库加载1.1VC++目录包含目录:即语句#include<x......
  • 数据库字段动态扩展设计
    最近讨论数据库有关产品方案的项目自动扩展问题,即每个方案都有多个项目,而每个方案的项目或多或少,也有不一样的,方案以后也可能随之增加新的项目。因此需要数据库设计一套可扩......
  • 小程序动态获取自定义Tabbar的高度
    1、在tabbar组件js文件的ready方法中加入以下代码ready(){constquery=wx.createSelectorQuery().in(this)query.select('.tab-bar').boundingClientR......
  • java动态代理和静态代理的实现
    代理模式:为其他对象提供一种代理以控制目标对象的访问,在某些情况下,一个对象不适合或者不能直接引用另外一个对象,代理对象可以在这个客户类和目标对象中起到一个桥梁作用。......