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

算法套路十七——买卖股票问题:状态机 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}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何应用金字塔模型提高结构化表达能力

看一下结构化表达的定义&#xff1a; 结构化表达&#xff1a;是基于结构化思维&#xff0c;理清事物整理与部分之间关系、换位思考后&#xff0c;进行简洁、清晰和有信服力的表达&#xff0c;是一种让受众听得明白、记得清楚、产生认同的精益沟通方式。 结构化表达的基本原则是…

总结如何申请注册 GitHub 教师教育优惠 Benefits for Teachers 来免费使用 copilot

目录 1. GitHub 教师教育优惠有什么2. 如何申请教师教育优惠呢2.1 选择学校2.2 更改个人信息2.3 准备证明材料2.4 提交申请2.5 遇到的问题2.5.1 问题 12.5.2 问题 22.5.3 问题 3 3. 申请免费的 GitHub Copilot 学生注册不在此处赘述了&#xff0c;网上有很多教程可以参考。但是…

前端BFC

一、首先我们要先了解常见的定位方案&#xff0c;总共3种&#xff08;普通流、浮动、绝对定位&#xff09; 而BFC是属于普通流的 我们可以把BFC看作为页面的一块渲染区域&#xff0c;他有着自己的渲染规则 简单来说BFC可以看作元素的一种属性&#xff0c;当元素拥有了BFC属性…

Python os模块详解

1. 简介 os就是“operating system”的缩写&#xff0c;顾名思义&#xff0c;os模块提供的就是各种 Python 程序与操作系统进行交互的接口。通过使用os模块&#xff0c;一方面可以方便地与操作系统进行交互&#xff0c;另一方面页也可以极大增强代码的可移植性。如果该模块中相…

二叉堆讲解

二叉堆讲解 大顶堆和小顶堆 从二叉堆的结构说起&#xff0c;它是一棵二叉树&#xff0c;并且是完全二叉树&#xff0c;每个结点中存有一个元素&#xff08;或者说&#xff0c;有个权值&#xff09;。 堆性质&#xff1a;父亲的权值不小于儿子的权值&#xff08;大根堆&#x…

什么是JS事件流

什么是JS事件流? 一&#xff1a;事件冒泡 <!DOCTYPE html> <html lang"en"> <head><title>事件冒泡例子</title> </head> <body><div id"box">点击我</div> </body> </html>上述的代…

利用暴力攻击破解登陆密码

长久以来&#xff0c;入侵远程计算机系统的工具和技术并没有发生翻天覆地的变化。例如&#xff0c;在许多情况下&#xff0c;普通用户只要知道了相关密码&#xff0c;就能立刻变身为管理员。虽然这些情形听起来不够曲折&#xff0c;但在大多数情况下&#xff0c;暴力攻击是通过…

css3 flex弹性布局详解

css3 flex弹性布局详解 一、flexbox弹性盒子 2009年&#xff0c;W3C 提出了一种新的方案----Flex 布局&#xff0c;可以简便、完整、响应式地实现各种页面布局。目前&#xff0c;它已经得到了所有浏览器的支持&#xff0c;这意味着&#xff0c;现在就能很安全地使用这项功能。…

【一起啃书】《机器学习》第五章 神经网络

文章目录 第五章 神经网络5.1 神经元模型5.2 感知机与多层网络5.3 误差逆传播算法5.4 全局最小与局部极小5.5 其他常见神经网络5.6 深度学习 第五章 神经网络 5.1 神经元模型 神经网络是由具有适应性简单单元组成的广泛并行互连的网络&#xff0c;它的组织能够模拟生物神经系统…

生产流程图怎么制作?思路提供

生产流程图是一种图表&#xff0c;用来展示生产流程中的各个环节及其顺序。这种图表可以帮助企业管理者更好地了解生产过程中的各个环节&#xff0c;从而更好地进行管理和优化。生产流程图通常包括各个生产环节的名称、所需时间、参与人员、设备和工具等信息。 在制作生产流程图…

七大软件架构设计原则详解

目录 1、概述 2、七大设计原则 2.1、开闭原则 2.2、里氏替换原则 2.3、依赖倒置原则 2.4、单一职责原则 2.5、接口隔离原则 2.6、迪米特法则 2.7、合成复用原则 3、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&…

基于jdk1.8的Java服务监控和性能调优

JVM的参数类型 X参数 非标准参数-Xint: 解释执行-Xcomp: 第一次使用就编译成本地代码-Xmixed: JVM自己来决定是否编译成本地代码 默认使用的是mixed mode 用的不多, 只需要做了解, 用的比较多的是XX参数 XX参数 非标准化参数相对不稳定主要用来JVM调优和Debug Boolean: …

【Vue3+TS项目】硅谷甄选day02--后台管理系统模板搭建/项目配置

1 项目初始化 一个项目要有统一的规范&#xff0c;需要使用eslintstylelintprettier来对我们的代码质量做检测和修复&#xff0c;需要使用husky来做commit拦截&#xff0c;需要使用commitlint来统一提交规范&#xff0c;需要使用preinstall来统一包管理工具。 1.1 环境准备 n…

阿里云u1服务器通用算力型CPU处理器性能测评

阿里云服务器u1通用算力型Universal实例高性价比&#xff0c;CPU采用Intel(R) Xeon(R) Platinum&#xff0c;主频是2.5 GHz&#xff0c;云服务器U1实例的基准vCPU算力与5代企业级实例持平&#xff0c;最高vCPU算力与6代企业级实例持平&#xff0c;提供2c-32c规格和1:1/2/4/8丰富…

elasticsearch结构化查询(一)

在上一篇中我们介绍了DSL相关的知识&#xff0c;接下来我们将会学习elasticsearch的结构化查询&#xff0c;同时也实践一下上一篇的DSL的查询用法 什么是结构化搜索? 从《Elasticsearch权威指南》上摘取部分解释如下: 结构化搜索是指查询包含内部结构的数据。日期&#xff0…

MATLAB 之 函数文件、特殊形式的函数和程序调试与优化

文章目录 一、函数文件1. 函数文件的基本结构2. 函数调用2.1 函数调用的格式2.2 函数的递归调用2.3 函数参数的可调性2.4 全局变量与局部变量 二、特殊形式的函数1. 子函数2. 内联函数3. 匿名函数 三、程序调试与优化1. 程序调试方法1.1 利用调试函数进行程序测试1.2 利用调试工…

MySQL数据库,JDBC连接数据库操作流程详细介绍

前言&#xff1a; 在学完 MySQL 和 Java 后&#xff0c;我们通常会尝试使用 Java编译器 连接 MySQL数据库&#xff0c;从而达到使用编译器来操作数据库的效果。连接的这个过程会用 JDBC 相关知识&#xff0c;因此我把 JDBC 包的下载及导入流程&#xff0c;以及 JDBC 的使用流程…

1.Buffer_Overflow-1.Basic_Jump

github上面的练习题 git clone https://github.com/Adamkadaban/LearnPwn 然后开始做 先进行 readelf 然后进行执行看看 是怎么回事 ./buf1发现就是一个输入和输出 我们checksec看看 发现stack 保护关闭 开启了NX保护 我们进入ida64看看反汇编 我习惯先看看字符串 SHITF…

C#异步编程之数据并行及任务并行

基于Parallel.ForEach的数据并行使用 1.数据非并行 var items new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; DateTime t1 DateTime.Now; foreach (var item in items) {Console.WriteLine("数据非并行输出:{0}", item); } 2.数据并行,只要使用Parallel.ForEach P…

Docker高频使用命令总结(镜像与容器命令)

目录 一.Docker常用命令总结 1.镜像命令管理 2.容器命令管理 二.Docker镜像操作命令 1.docker search&#xff1a;搜索镜像 2.docker pull&#xff1a;下载镜像 3.docker push&#xff1a;上传镜像 4.docker images&#xff1a;查看本地镜像 5.docker inspect &#x…