Python - 深夜数据结构与算法之 股票问题大全

目录

一.引言

二.股票问题通式

1.股票买卖分析

2.股票买卖状态转移

三.股票问题全解

1.股票交易 - 交易 1 次

2.股票交易 - 交易多次

3.股票交易 - 交易 2 次

4.股票交易 - 交易 K 次

5.股票交易 - 含冷冻期

6.股票交易 - 含交易费

四.总结


一.引言

给定一定日期内的股票价格,求交易 1 次、2 次、K 次、带冷冻期、带交易费等衍生出多道股票买卖的问题,今天通过一个模版搞定所有股票问题。

二.股票问题通式

1.股票买卖分析

股票买卖的本质就是三种状态,买 buy、卖 sell 以及保持 rest:

- rest 情况下,可以保持持有股票、可以保持没有股票

- buy 情况下,必须是在未拥有股票的情况下

- sell 情况下,必须是在拥有股票的情况下

基于天数 i、交易次数 k 以及是否持有股票 [0, 1],我们可以得到总状态数:

2.股票买卖状态转移

基于上面的总状态数,我们可以计算第 i 天第 k 次交易时我们手中的最大收益。

三.股票问题全解

1.股票交易 - 交易 1 次

链接: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """

        # dp[i] = max(dp[i] - pre_min, dp[i-1])

        dp = [0] * len(prices)
        pre_min = prices[0]

        for i in range(1, len(prices)):
            dp[i] = max(prices[i] - pre_min, dp[i-1])
            if pre_min > prices[i]:
                pre_min = prices[i]

        return dp[-1]

2.股票交易 - 交易多次

 链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """

        # 天数 i = len(prices) 交易次数 k = 1 状态 0/1 拥有,不拥有
        dp = [[0, 0] for _ in range(len(prices))]

        # 初始状态
        dp[0][0] = 0
        dp[0][1] = -1 * prices[0]

        # 递推
        for i in range(1, len(prices)):
            # 不持有 = 保持昨天状态 or 昨天有股票且今天卖了
            dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
            # 持有 = 保持昨天状态 or 昨天没股票且今天买了
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
        
        return max(dp[-1][0], dp[-1][1])

3.股票交易 - 交易 2 次

class Solution(object):
    def maxProfit(self, prices):
        """
        :type prices: List[int]
        :rtype: int
        """

        # 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
        dp = [[0, 0, 0, 0] for _ in range(len(prices))]

        # 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
        dp[0][0] = -1 * prices[0]
        dp[0][1] = 0
        dp[0][2] = -1 * prices[0]
        dp[0][3] = 0

        for i in range(1, len(prices)):
            # 第一次买票 - Rest 或者 买今天更便宜的
            dp[i][0] = max(dp[i-1][0], -prices[i])
            # 第一次卖票 - Rest 或者 今天把昨天的卖了
            dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
            # 第二次买票 - Rest 或者 上一次卖完再买现在的
            dp[i][2] = max(dp[i-1][2], dp[i-1][1] - prices[i])
            # 第二次卖票 - Rest 或者 卖了第二次买的
            dp[i][3] = max(dp[i-1][3], dp[i-1][2] + prices[i])
        
        return dp[-1][-1]

4.股票交易 - 交易 K 次

链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/

class Solution(object):
    def maxProfit(self, k, prices):
        """
        :type k: int
        :type prices: List[int]
        :rtype: int
        """

        if len(prices) <= 1:
            return 0

        k = min(k, len(prices) // 2)

        # 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
        dp = [[0, 0] * k for _ in range(len(prices))]

        # 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
        for i in range(len(dp[0])):
            dp[0][i] = -1 * prices[0] * ((i + 1) % 2)

        for i in range(1, len(prices)):
            # 第一次买票 - Rest 或者 买今天更便宜的
            dp[i][0] = max(dp[i-1][0], -prices[i])
            for j in range(1, len(dp[0])):
                dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + (-1)** (j+1) * prices[i])
        
        return dp[-1][-1]

交易 K 次我们可以模仿交易 2 次的 4 种状态进行归纳总结。 

5.股票交易 - 含冷冻期

链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/

class Solution(object):
    def maxProfit(self, prices):
        
        """
            0 - 目前有一只股票对应的最大收益
            1 - 目前没有股票,处于冷冻期的最大收益
            2 - 目前没有股票,不处于冷冻期

        """

        N = len(prices)
        dp = [[0] * 3 for _ in range(N)]
        dp[0][0] = -prices[0]
        dp[0][1], dp[0][2] = 0, 0

        for i in range(1, N):
            # 第 i 天有一只股票: A.还拿着上一天的股票 dp[i-1][0] B.昨天没股票且能买 dp[i-1][2] - price
            dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
            # 第 i 天处于冷冻期 A.i-1天卖股票了 说明 i-1 天手里必须有股票 -> dp[i-1][0] + price
            dp[i][1] = dp[i-1][0] + prices[i]
            # 第 i 天没股票且非冷冻期 A.昨天也没股票也非冷冻 dp[i-1][2] B.昨天没股票冷冻 dp[i-1][1]
            dp[i][2] = max(dp[i-1][2], dp[i-1][1])
        
        return max(dp[-1])

6.股票交易 - 含交易费

链接: https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/

class Solution(object):
    def maxProfit(self, prices, fee):
        """
        :type k: int
        :type prices: List[int]
        :rtype: int
        """

        if len(prices) <= 1:
            return 0

        k = len(prices) // 2

        # 天数 i = len(prices) 交易次数 k = 2 状态 0/1 拥有,不拥有
        dp = [[0, 0] * k for _ in range(len(prices))]

        # 初始化状态 分别代表第 i 次买、卖股票时手里最大收益 Profit
        for i in range(len(dp[0])):
            dp[0][i] = -1 * (prices[0] + fee) * ((i + 1) % 2)

        for i in range(1, len(prices)):
            # 第一次买票 - Rest 或者 买今天更便宜的
            dp[i][0] = max(dp[i-1][0], -prices[i] - fee)
            for j in range(1, len(dp[0])):
                action = (-1) ** (j+1)
                if action > 0:
                    profit = prices[i]
                else:
                    profit = -prices[i] - fee

                dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + profit)
        
        return dp[-1][-1]

 上面的方法通式易于理解,但是随着交易次数的增加,DP 状态空间会扩大。

这里还有另一个简单的双指针考虑法,大家可以体会下,本文主要体会上面 DP 转移方程解题的,这里不多赘述:

class Solution:
    def maxProfit(self, prices, fee):

        buy, sell = -float("inf"), 0

        for p in prices:
            buy = max(buy, sell - p - fee)
            sell = max(sell, buy + p)
        
        return sell

四.总结

这里作为自己的笔记整理了 6 道股票相关的问题,主要是体会 DP 转移方程以及两种状态 [0, 1] 没股票和有股票,在冷冻期一题我们还需增加状态 2 即冷冻期。这里交易 K 次和交易多次,都可以通过交易 2 次的写法推广至 For 循环交易多次。而交易费则只需要在 buy 的时候多减去一个 fee 即可。

Tips:

这里再解释下为什么第一天买股票时 profit 为 - prices[0],因为我们的起始收益为 0,如果我们第一天购买股票,则我们的收益是 0 - prices[0],所以此时的收益为 - prices[0]。而多次交易时多次购买的起始收益都为 - prices[0] 这里可以理解为第一天连续买卖多次,多次买卖结束后收益为 0,因为是一天多次交易。而再下一次购买时,和初始的第一次买是一样的情况,所以初始化收益也是 - prices[0]。 

为了实现Google Gmail注册功能,通常不会直接提供完整的源代码示例来创建Gmail账户。这是因为用户账户管理涉及敏感操作,应由官方服务处理以确保安全性和合规性。 然而,在开发与Gmail交互的应用程序时,可以利用OAuth 2.0协议授权流程来进行身份验证和访问控制[^3]。这允许第三方应用请求特定权限范围内的数据访问而无需知晓用户的密码。 对于希望集成Google登录或与其他Google服务互动的应用开发者来说,建议按照官方指南设置项目并启用必要的API接口: - 创建新的Google应用程序需前往Google API Console页面[^1]。 ```python import os from google_auth_oauthlib.flow import InstalledAppFlow from googleapiclient.discovery import build SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'] def main(): """Shows basic usage of the Gmail API. Lists the user's Gmail labels. """ creds = None flow = InstalledAppFlow.from_client_secrets_file( 'credentials.json', SCOPES) creds = flow.run_local_server(port=0) service = build('gmail', 'v1', credentials=creds) results = service.users().labels().list(userId='me').execute() labels = results.get('labels', []) if not labels: print('No labels found.') else: print('Labels:') for label in labels: print(label['name']) if __name__ == '__main__': main() ``` 此Python脚本展示了如何通过OAuth 2.0认证过程连接到Gmail API,并列出当前用户的标签列表作为简单演示。请注意,实际部署前还需要考虑更多细节配置以及错误处理机制等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BIT_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值