算法套路十七——买卖股票问题:状态机 DP

news2024/11/28 14:41:56

算法套路十七——买卖股票问题:状态机 DP

状态机DP是一种将动态规划方法应用于有限状态机(Finite State Machine)的问题求解方法。
状态机DP(State Machine DP)是一种动态规划的思想,它通常用于解决一些具有状态转移的问题。在状态机DP中,我们将问题抽象成一个状态机,其中每个状态表示问题的一个子问题,每个状态之间存在状态转移,表示从一个子问题转移到另一个子问题的过程。状态机DP方法也适用于涉及多个子问题之间存在依赖关系的问题,例如字符串匹配、序列比较等。
买卖股票问题是一种典型的状态机 DP问题,设"未持有股票"和"持有股票"两个状态,每个节点表示某一状态下的最大收益,相邻节点之间的边表示在当前状态下进行一次交易得到的收益。

在这里插入图片描述

算法示例一:LeetCode122. 买卖股票的最佳时机 II

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
在这里插入图片描述

法一:递归+记忆化搜索

  1. 递归函数:dfs(i, hold)表示到第i日,手上是否拿着股票所得到的最大收益。hold为true则表示有股票,为false则表示没有股票
  2. 转移方程:
    • 如果第 i i i天持有股票,那么当前最大收益由两种可能转移而来:
      • 在第 i − 1 i - 1 i1天持有股票的情况下并选择不卖出:此时收益就是 d f s ( i − 1 , T r u e ) dfs(i - 1, True) dfs(i1,True)
      • 在第 i − 1 i - 1 i1天不持有股票的情况下并选择买入:此时收益就是 d f s ( i − 1 , F a l s e ) − p r i c e s [ i ] dfs(i - 1, False) - prices[i] dfs(i1,False)prices[i],当前利润减去当天价格。
      • 取两者最大值作为转移结果。
    • 如果第 i i i天未持有股票,那么当前最大收益由两种可能转移而来:
      • 在第 i − 1 i - 1 i1天未持有股票的情况下并选择不购买:此时收益就是 d f s ( i − 1 , F a l s e ) dfs(i - 1, False) dfs(i1,False)
      • 在第 i − 1 i - 1 i1天持有股票的情况下并选择卖出:此时收益就是 d f s ( i − 1 , T r u e ) + p r i c e s [ i ] dfs(i - 1, True) + prices[i] dfs(i1,True)+prices[i],因为当前不具有股票而获得了可用资金。
      • 取两者最大值作为转移结果。
        在这里插入图片描述
  3. 边界值:当遍历完所有即i<0时,如果持有股票,返回负无穷(代表不合法状态);否则返回0
  4. 返回值:dfs(n - 1, False)
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices) 
        @cache  # 使用缓存装饰器,加速递归函数计算
        # i 表示当前考虑到的股票价格下标,hold 表示当前是否持有股票
        def dfs(i: int, hold: bool) -> int:
            if i < 0:  # 如果已经遍历完所有股票价格
                return -inf if hold else 0  # 如果持有股票,返回负无穷(代表不合法状态);否则返回零利润。
            # 如果当前已经持有一支股票 
            if hold:      
                #卖或不卖    
                return max(dfs(i - 1, True), dfs(i - 1, False) - prices[i]) 
            #买或不买    
            return max(dfs(i - 1, False), dfs(i - 1, True) + prices[i])
        return dfs(n - 1, False) 

法二:二维dp数组动态规划

直接利用上述递归+记忆化搜索代码转换为动态规划
dp[i][0]表示第i天结束后手里没有股票的最大收益,dp[i][1]表示第i天结束后手里持有一支股票的最大收益,最后返回dp[n][0],表示在最后一天卖出所有手头的股票后得到的最大收益

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)  
        dp = [[0] * 2 for _ in range(n + 1)]
        dp[0][1]=-inf
        for i, price in enumerate(prices):
            #当前没有股票,只能不买或卖
            dp[i+1][0]=max(dp[i][0],dp[i][1]+price)
             #当前有股票,只能买或不卖
            dp[i+1][1]=max(dp[i][0]-price,dp[i][1])
        return dp[n][0]

算法练习一:LeetCode714. 买卖股票的最佳时机含手续费

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。
你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
返回获得利润的最大值。
注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
在这里插入图片描述

与示例一样的思路,只是需要在购买时不仅减去股票价格,并减去手续费

func maxProfit(prices []int, fee int) int {
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, 2)
    }
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
    	//不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //买并支付小费
        dp[i+1][1] = max(dp[i][0]-price-fee, dp[i][1])
        
    }
    return dp[n][0]
}
func max(x, y int) int {if x > y {return x};return y}

算法练习二:LeetCode309. 最佳买卖股票时机含冷冻期

给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。​设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

本题与示例唯一的差距在于多了冷冻期,而这也只会影响买股票这一种情况,由于冷冻期,所以在计算买股票这种情况时,只能用2天前且手上没有股票的最大利润来计算,且第一天结束时不存在前一天买入的情况,因此我们需要对第一天单独处理。

func maxProfit(prices []int) int {
    n := len(prices)
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, 2)
    }
    //-math.MaxInt32表示不符合现实
    dp[0][1] = -math.MaxInt32
    for i, price := range prices {
        //当前没有股票,只能不买或卖
        dp[i+1][0] = max(dp[i][0], dp[i][1]+price)
        //当前有股票,只能买或不卖
        if i>0{
        //i>0时,对于买的情况,由于冷冻期所以只能用dp[i-1][0]即2天前且手上没有股票的最大利润
        dp[i+1][1] = max(dp[i-1][0]-price, dp[i][1])
        }else {
            //i=0即第一天想要有股票只能买
            dp[i+1][1] =-price
        }
    }
    return dp[n][0]
}
func max(x, y int) int {if x > y {return x};return y}

算法练习三:LeetCode123. 买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

递归+记忆化搜索

首先思考递归+记忆化搜索来开拓思路,易知可以在递归中添加一个参数j,来记录当前遍历次数,代码如下所示

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        n = len(prices)
        # 使用cache装饰器来实现记忆化搜索
        @cache
        def dfs(i: int, j: int, hold: bool) -> int:
            # 如果j小于0,表示交易次数已经用完,返回负无穷
            if j < 0:
                return -inf
            # 如果i小于0,表示已经到达第0天,如果持有股票,返回负无穷,否则返回0
            if i < 0:
                return -inf if hold else 0
            if hold:
                return max(dfs(i - 1, j, True), dfs(i - 1, j - 1, False) - prices[i])
            else:
                return max(dfs(i - 1, j, False), dfs(i - 1, j, True) + prices[i])
        return dfs(n - 1, 2, False)

动态规划

根据以上递归过程转换为动态规划,递归函数添加了一个参数,所以我们在动态数组dp中添加一维交易次数,其中dp[i][j][0]表示第i天,最多进行j次交易,且当前不持有股票的最大收益,dp[i][j][1]表示第i天,最多进行j次交易,且当前持有股票的最大收益。

不过需要注意,只有买时才算一次新的交易次数,而卖不算一次新的交易

func maxProfit(prices []int) int {
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
        dp[i]=make([][2]int,3)
        for j:=0;j<3;j++{
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
        for j:=0;j<2;j++{
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][2][0]
}
func max(x, y int) int {if x > y {return x};return y}

算法练习四:LeetCode188. 买卖股票的最佳时机 IV

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格,和一个整型 k 。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
在这里插入图片描述

直接采用上题的思路,将j的范围由0-2修改到0-k即可

func maxProfit(k int, prices []int) int {
    n := len(prices)
    dp := make([][][2]int, n+1)
    for i:=range dp{
        dp[i]=make([][2]int,k+1)
        for j:=0;j<k+1;j++{
            dp[i][j][1] = math.MinInt32/2
        }
    }
    for i:=0;i<n;i++{
        for j:=0;j<k;j++{
            //不买或卖
            dp[i+1][j+1][0]=max(dp[i][j+1][0],dp[i][j+1][1]+prices[i])
            //不卖或买,买即增加一次交易
            dp[i+1][j+1][1]=max(dp[i][j+1][1],dp[i][j][0]-prices[i])
        }
    }
    return dp[n][k][0]
}
func max(x, y int) int {if x > y {return x};return y}

算法练习五:LeetCode1911. 最大子序列交替和

一个下标从 0 开始的数组的 交替和 定义为 偶数 下标处元素之 和 减去 奇数 下标处元素之 和 。
比方说,数组 [4,2,5,3] 的交替和为 (4 + 5) - (2 + 3) = 4 。
给你一个数组 nums ,请你返回 nums 中任意子序列的 最大交替和 (子序列的下标 重新 从 0 开始编号)。
一个数组的 子序列 是从原数组中删除一些元素后(也可能一个也不删除)剩余元素不改变顺序组成的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的一个子序列(加粗元素),但是 [2,4,2] 不是。
在这里插入图片描述

类比买卖股票,区别在于本题刚开始就已经0元买进了股票,故初始化数组dp[0][1] =0,dp[0][0] =math.MinInt64 / 2,其余则和示例思路一样

func maxAlternatingSum(nums []int) int64 {
    //类比买卖股票,即第一次0元买进,之后卖出
    n := len(nums)
    dp := make([][]int, n+1)
    for i := range dp {
        dp[i] = make([]int, 2)
    }
    //负无穷表示不符合题意,/2防止越界
    dp[0][0] =math.MinInt64 / 2
    //开始0元买进了股票
    dp[0][1] =0
    for i, num := range nums {
        dp[i+1][0] = max(dp[i][0], dp[i][1]+num)
        dp[i+1][1] = max(dp[i][0]-num, dp[i][1])
    }
    return int64(dp[n][0])
}
func max(x, y int) int {if x > y {return x};return y}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/537611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

行动元宣布推出面向精密制造领域的智能运动控制解决方案

近日&#xff0c;AI 工业工程化平台行动元宣布推出面向精密制造领域的智能运动控制解决方案。该方案融合大数据模型、数字孪生以及人工智能技术&#xff0c;通过数字化建模、适配、调试等过程&#xff0c;极大提升终端设备集成方案的设计、选型与测试效率&#xff0c;并通过算法…

你不学,我不学,谁来网安,谁来保卫国家!

一、为什么选择网络安全&#xff1f; 这几年随着我国**《国家网络空间安全战略》《网络安全法》《网络安全等级保护2.0》**等一系列政策/法规/标准的持续落地&#xff0c;网络安全行业地位、薪资随之水涨船高。 未来3-5年&#xff0c;是安全行业的黄金发展期&#xff0c;提前…

PCL点云库(6) — Filters模块空间裁剪器类

目录 6.1 3D包围盒裁剪器Class BoxClipper3D< PointT > 6.2 平面裁剪器Class pcl::PlaneClipper3D< PointT > 6.3 立方体过滤Class pcl::CropBox< PointT > 6.4 曲面或多边形过滤 Class pcl::CropHull< PointT > 6.5 完整代码 6.1 3D包围盒裁剪器…

C语言基础:static关键字

本文结合工作经验&#xff0c;研究C语言中static关键字的用法。 文章目录 1 static关键字概念2 用法与使用场景2.1 修饰全局变量2.1.1 代码示例2.1.2 使用场景 2.2 修饰函数2.2.1 代码示例2.2.2 使用场景 2.3 修饰局部变量2.3.1 代码示例2.4.2 使用场景 3 总结 1 static关键字…

第13章:存储过程和存储函数

一、存储过程 1.1理解 含义&#xff1a; 存储过程stored procedure&#xff0c;思想是一组经过预先编译的SQL语句的封装。 存储过程预先存储在MySQL服务器上&#xff0c;需要执行的时候&#xff0c;客户端向服务器端发出调用存储过程的命令&#xff0c;服务器段把这组SQL执…

当我和ChatGPT-4聊完后,我觉得一切可能已经来不及了

飞机上有wifi&#xff0c;了然无味&#xff0c;在万米高空&#xff0c;和ChatGPT-4开始了一场坦诚的沟通&#xff0c;它全程都表现出高情商&#xff0c;以及不断尽量安抚我的情绪&#xff0c;而这&#xff0c;恰恰令我脊背发凉。 部分文字截取 ZM&#xff1a;我能不能理解每次对…

k8s学习-CKS真题-ImagePolicyWebhook容器镜像扫描

目录 题目环境搭建imagePolicyWebhook搭建 解题任务二任务三任务一检查 模拟考题参考 题目 Context cluster上设置了容器镜像扫描器&#xff0c;但尚未完全集成到 cluster 的配置中。 完成后&#xff0c;容器镜像扫描器应扫描并拒绝易受攻击的镜像的使用。 Task 注意&#xff…

5.17 ARM 作业

1. 2.用for循环实现1~100之间的和 13BA 3.xmind

可以找工作的C端的低代码产品,终于让我找到了

目录 写在前面 低代码平台 平台怎么选 各平台区别 为什么选它 写在前面 大家都知道低代码这个叫法是从B端叫起来的&#xff0c;也就是说不管是业务人员还是开发人员&#xff0c;都是企业内部使用。那么有没有C端的&#xff0c;且免费使用的低代码产品呢&#xff1f; 低代码…

一次性能优化思考过程

前言 最近业务上空闲了下来&#xff0c;也是把之前在开发时自身感受比较大的白屏时间放在了主线上去排查优化&#xff0c;这里记录一下笔者对于移动端vConsole脚本的引入问题全过程。 网络脚本与问题定位 对于白屏时间&#xff0c;与网络传输有很大关系&#xff0c;如图&…

该怎样学习网络安全知识?

首先&#xff0c;必须&#xff08;时刻&#xff09;意识到你是在学习一门可以说是最难的课程&#xff0c;是网络专业领域的顶尖课程&#xff0c;不是什么人、随随便便就能学好的。不然&#xff0c;大家都是黑客&#xff0c;也就没有黑客和网络安全的概念了。 很多朋友抱着学一…

#systemverilog# 之 event region 和 timeslot 仿真调度(五)实战

目录 一 问题代码 二 解决方法 2.1 调换代码顺序 2.2 #0 Delay 2.3 uvm class 执行移到re-avtive 2.4 搭建完备的UVM 验证平台 三 预期波形 经过之前文章的学习&#xff0c;想必大家对systemverilog 仿真调度的理解&#xff0c;应该八九不离十了。今天&#xff0c;我们…

基于STM32的NRF24L01 2.4G通讯模块的驱动实验(HAL库)

前言&#xff1a;本文为手把手教学NRF24L01 2.4G通讯模块的驱动实验&#xff0c;本教程的 MCU 采用STM32F103ZET6与STM32F103C8T6&#xff0c;彼此进行互相通讯。通过 CubeMX 软件配置 SPI 协议驱动NRF24L01 2.4G通讯模块&#xff08;HAL库&#xff09;。NRF24L01 2.4G是嵌入式…

渗透测试--5.2.hash密码的破解

目录 1.hashcat简介 2.hashcat参数 常见参数 哈希类型&#xff08;-m&#xff09; 破解模式&#xff08;-a&#xff09; 3.实例 步骤一&#xff1a;使用hash-identifier工具判断哈希值类型 步骤二&#xff1a;使用字典攻击进行破解 1.hashcat简介 hashcat号称世界上最…

js实现点击改变文字大小

目录 一、前言二、代码实现 一、前言 在编写代码之前我们先来看看通过js获取元素有几种方式&#xff1a; 1.第一种 document.querySelector() 返回文档中匹配指定的选择器组的第一个元素document.querySelectorAll(); 返回文档中匹配指定的选择器组的所有元素 <!DOCTYPE ht…

vector容器 [上]

目录 一、 对于vector的介绍 二、vector的定义 0x01 无参构造 0x02 构造并初始化n个val 0x03 使用迭代器进行初始化构造 0x04 拷贝构造 0x05 比较 三、 vector的遍历 0x01 push_back() 0x02 operator[] 和at() 0x03 遍历 四、vector 容量空间 0x01 max_size : 返回v…

【设置教程】未使用系统分配DNS地址 如何设置?答:

未使用系统分配DNS地址&#xff0c;是你的域名解析DNS地址不是当前系统的DNS地址。 设置方法&#xff1a;先关闭更新。域名控制台--域名列表--安全设置&#xff1a; 通过短信验证。 2、修改DNS地址&#xff1a;域名控制台--域名列表&#xff1a;点击域名&#xff1a; 3、点击&…

C语言函数速查

scanf函数 函数概要&#xff1a; scanf 函数从标准输入流中读取格式化字符串。 与 printf 格式化输出函数相反&#xff0c;scanf 函数是格式化输入函数。 函数原型&#xff1a; #include <stdio.h> ... int scanf(const char *format, ...);参数解析&#xff1a; 1…

事务 ---MySQL的总结(六)

事务 多进程进行并改变同一个数据&#xff0c;如果没有进行版本控制&#xff0c;就会出现数据不确定的问题&#xff0c;为此引入了事务的概念。可以进行数据回滚&#xff0c;解决潜在的问题。 事务的概念 一组的DML组成&#xff0c;这一些的DML要么同时成功&#xff0c;要么同…

Linux上开启coredump

Linux上开启core dump Core dump&#xff08;核心转储&#xff09;是在程序崩溃时生成的一种文件&#xff0c;其中包含了程序在崩溃时的内存状态信息。它可以帮助程序员在调试程序时快速定位问题&#xff0c;并且是一种非常有用的调试工具。core dump的作用如下&#xff1a; 帮…