60题学会动态规划系列:动态规划算法第四讲

news2024/11/17 11:38:52

买卖股票相关的动态规划题目

文章目录

  • 1.买卖股票的最佳时机含冷冻期
  • 2.买卖股票的最佳时期含⼿续费
  • 3.买卖股票的最佳时机III
  • 4.买卖股票的最佳时机IV


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

力扣链接:力扣

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 首先我们分析一下题目,题目中的要点是卖出股票后第二天不能买入,并且每次买新的股票前都要出售掉原先的股票,有了这个限制条件,我们就很容易分析出这道题是多状态的dp。

1.状态表示

当我们以dp[i]表示第i天结束的最大利润时,我们发现无法写出状态转移方程,因为要求第i天的最大利润,我们要看第i天是否是冷冻期或者是否手中无股票或者手中有股票,所以我们将有三种状态表示:

f[i]表示第i天手中有股票的最大利润

g[i]表示第i天手中没有股票的最大利润

s[i]表示第i天处于冷冻期的最大利润

2.状态转移方程

首先我们要分析每种状态,比如我们第i天持有股票,那么从哪一个状态可以到有股票的状态呢?当前一天也就是i-1天就有股票的时候,我们什么也不干到了第i天还是处于有股票的状态。当前一天是没有股票的状态,那么我们在前一天买股票到了第i天就处于有股票状态。

所以f[i] = max(f[i-1],g[i-1] - p[i])

接下来我们分析没有股票的状态,首先如果前一天就没有股票,那么什么也不干到了第i天还是处于没有股票的状态。如果前一天是冷冻期,那么什么也不干到了第i天就自动处于没有股票状态(因为冷冻期一定是卖出股票了,一旦卖出手中就没有股票了)。

所以 g[i] = max(g[i-1],s[i-1])

接下来我们分析冷冻期,冷冻期一定是卖出股票才会有的,所以前一天是有股票状态,然后将股票卖出,第i天就是冷冻期。

所以s[i] = f[i-1] + p[i];

3.初始化

从状态转移方程我们可以看到每次需要前一天的利润,那么只有第1天会越界,所以我们直接初始化三个表的第一天,第一天要有股票那么就得买入,买入利润就从0变成负数,所以f[0] = -p[0]

第一天没有股票那么什么也不干就可以,所以g[0] = 0

第一天就处于冷冻期那么利润一定为0 所以s[0] = 0

4.填表

从左向右,三个表一起填

5.返回值

返回三个表的最后一天的最大值。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> f(n,0),g(n,0),s(n,0);
        f[0] = -prices[0];
        for (int i = 1;i<n;i++)
        {
            f[i] = max(f[i-1],g[i-1]-prices[i]);
            g[i] = max(g[i-1],s[i-1]);
            s[i] = f[i-1]+prices[i];
        }
        return max(f[n-1],max(g[n-1],s[n-1]));
    }
};

当然我们也可以将代码优化一下,最后一天如果手里还有股票没卖出去,那么这一天的利润一定是比无股票状态和冷冻期状态低的,所以我们只需要返回卖出股票状态的最大值即可:

当然,我们上面用三个一维数组表示状态是比较冗余的,我们可以用二维数组来表示,代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> dp(n,vector<int>(3,0));
        dp[0][0] = -prices[0];
        for (int i = 1;i<n;i++)
        {
            dp[i][0] = max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][1] = max(dp[i-1][1],dp[i-1][2]);
            dp[i][2] = dp[i][0] + prices[i];
        }
        return max(dp[n-1][1],dp[n-1][2]);
    }
};

 上面我们是以dp[i][0]表示有股票状态,dp[i][1]表示无股票状态,dp[i][2]表示冷冻期。

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

力扣链接:力扣

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

 这道题是我们做的第一道题的变种,我们先来分析一下这道题中的细节:

首先这次没有冷冻期了可以随便交易,但是每笔交易需要付手续费,这里要注意了,一笔交易是指有股票然后卖出,可以理解为只有卖出的时候需要交手续费。并且这道题和第一题一样,都是只有卖出原先的股票才能购买新的股票。

1.状态表示

我们根据上一题的经验,直接用f[i]表示第i天手中有股票的最大利润,用g[i]表示第i天手中没有股票的最大利润。

2.状态转移方程

 因为此题只有两种状态,所以我们直接分析:

当前一天也就是i-1天就有股票的时候,我们什么也不干到了第i天还是处于有股票的状态。当前一天是没有股票的状态,那么我们在前一天买股票到了第i天就处于有股票状态。

所以f[i] = max(f[i-1],g[i-1]-p[i])

首先如果前一天就没有股票,那么什么也不干到了第i天还是处于没有股票的状态。如果前一天有股票,那么我们卖出股票就变成了没有股票状态。

所以g[i] = max(g[i-1],f[i-1] + p[i] -fee)   //注意卖出股票需要支付手续费

3.初始化

只有第一天会越界,所以我们直接初始化两个表的第一天的最大利润:

第一天要有股票那么就得买入,买入利润就从0变成负数,所以f[0] = -p[0]

第一天没有股票那么什么也不干就可以,所以g[0] = 0

4.填表

从左向右,两个表一起填

5.返回值

返回最后一天是卖出状态的最大利润即可。(因为最后一天手中没股票一定比手中有股票的利润大)

class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
       int n = prices.size();
       vector<int> f(n,0),g(n,0);
       f[0] = -prices[0];
       for (int i = 1;i<n;i++)
       {
           f[i] = max(f[i-1],g[i-1]-prices[i]);
           g[i] = max(f[i-1]+prices[i]-fee,g[i-1]);
       }
       return g[n-1];
    }
};

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

力扣链接:力扣

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 这道题和我们上一题基本一样,就是多了一个最多完成两笔交易的限制,下面我们直接开始分析。

1.状态表示

我们根据前两道题的经验,先以f[i]表示第i天手中有股票的最大利润,g[i]表示第i天手中没有股票的最大利润,但是我们发现这样的状态表示无法限制最多完成两笔交易,所以我们直接多加一个状态就可以了,用f[i][0]代表第i天进行了0笔交易手中有股票的最大利润,f[i][1]代表第i天进行了1笔交易手中有股票的最大利润,f[i][2]代表第i天进行了2笔交易手中有股票的最大利润,g表同理。

所以f[i][j]代表第i天交易了j次,处于有股票状态。

g[i][j]代表第i天交易了j次,处于没有股票的状态。

2.状态转移方程

当前一天也就是i-1天就有股票的时候,我们什么也不干到了第i天还是处于有股票的状态。当前一天是没有股票的状态,那么我们在前一天买股票到了第i天就处于有股票状态。

所以f[i][j] = max(f[i-1][j],g[i-1][j]-p[i])  注意:前一天处于有股票的状态,那么什么也不干第i天还是处于有股票的状态,所以我们的交易次数是不变的,还是j次。如果前一天是没有股票状态,那么买了股票就到了有股票状态,但是我们要注意只有卖出股票才算一次交易,所以这里还是j次交易没有改变。

首先如果前一天就没有股票,那么什么也不干到了第i天还是处于没有股票的状态,并且交易次数不发生改变。如果前一天有股票,那么我们卖出股票就变成了没有股票状态,但是卖出股票就会增加一次交易,而我们要求的实际上是第i天的交易,也就是说增加完一次交易后交易次数才变成了j,那么在求前一天的有股票的利润时应该按照j-1的交易次数(因为前一天有股票,第i天卖出变成没有股票状态,一旦卖出交易次数+1,默认第i天是j次交易的话,那么第i-1天就是j-1次交易)

所以g[i] = max(g[i-1][j],f[i-1][j-1]+p[i])

3.初始化

通过状态转移方程可以发现,每次要求前一天相应交易次数的最大值,而为了原来表中的数据不影响取最大值,就将表中每个数据初始化为整形的最小值,但是由于有-p[i]的存在,会使整形的最小值溢出,所以我们只取一半整形的最小值就好了。

第一天要有股票并且不交易(也就是不卖出)那么利润就从0变成负数,所以f[0][0] = -p[0]

第一天没有股票并且交易次数为0,那么什么也不干就可以,所以g[0][0] = 0

4.填表

每一行从上往下,每一列从左向右,两个表一起填

5.返回值

返回最后一天是卖出状态的并且交易是0,1,2三种中的最大利润即可。(因为题目只限制不超过2笔交易,但是不能保证交易次数多一定利润大,当只有一天的股票的时候,不交易是利润最大的)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        const int Min = -0x3f3f3f3f;
        vector<vector<int>> f(n,vector<int>(3,Min));
        auto g = f;
        f[0][0] = -prices[0];
        g[0][0] = 0;
        for (int i = 1;i<n;i++)
        {
            for (int j = 0;j<3;j++)
            {
                f[i][j] = max(f[i-1][j],g[i-1][j]-prices[i]);
                g[i][j] = g[i-1][j];
                if (j>=1)
                {
                    g[i][j] = max(g[i-1][j],f[i-1][j-1]+prices[i]);
                }
            }
        }
        int ret = g[n-1][0];
        for (int i = 1;i<3;i++)
        {
            if (ret<g[n-1][i])
            {
                ret = g[n-1][i];
            }
        }
        return ret;
    }
};

需要注意的是,我们的f[i-1][j-1]这种情况只有在j>=1的时候才不会越界,所以当j = 0的时候我们只需要让g[i][j] = g[i-1][j]

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

力扣链接:力扣

给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格,和一个整型 k 。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 其实大家不难发现,这道题和我们上一题的区别只有交易的最大限制,而我们也只需要将上一题的两笔交易修改为k笔交易即可。

1.状态表示

f[i][j]代表第i天交易了j次,处于有股票状态。

g[i][j]代表第i天交易了j次,处于没有股票的状态。

2.状态转移方程

当前一天也就是i-1天就有股票的时候,我们什么也不干到了第i天还是处于有股票的状态。当前一天是没有股票的状态,那么我们在前一天买股票到了第i天就处于有股票状态。

所以f[i][j] = max(f[i-1][j],g[i-1][j]-p[i])  注意:前一天处于有股票的状态,那么什么也不干第i天还是处于有股票的状态,所以我们的交易次数是不变的,还是j次。如果前一天是没有股票状态,那么买了股票就到了有股票状态,但是我们要注意只有卖出股票才算一次交易,所以这里还是j次交易没有改变。

首先如果前一天就没有股票,那么什么也不干到了第i天还是处于没有股票的状态,并且交易次数不发生改变。如果前一天有股票,那么我们卖出股票就变成了没有股票状态,但是卖出股票就会增加一次交易,而我们要求的实际上是第i天的交易,也就是说增加完一次交易后交易次数才变成了j,那么在求前一天的有股票的利润时应该按照j-1的交易次数(因为前一天有股票,第i天卖出变成没有股票状态,一旦卖出交易次数+1,默认第i天是j次交易的话,那么第i-1天就是j-1次交易)

所以g[i] = max(g[i-1][j],f[i-1][j-1]+p[i])

3.初始化

通过状态转移方程可以发现,每次要求前一天相应交易次数的最大值,而为了原来表中的数据不影响取最大值,就将表中每个数据初始化为整形的最小值,但是由于有-p[i]的存在,会使整形的最小值溢出,所以我们只取一半整形的最小值就好了。

第一天要有股票并且不交易(也就是不卖出)那么利润就从0变成负数,所以f[0][0] = -p[0]

第一天没有股票并且交易次数为0,那么什么也不干就可以,所以g[0][0] = 0

4.填表

每一行从上往下,每一列从左向右,两个表一起填

5.返回值

返回最后一天是卖出状态的并且交易是K种中的最大利润即可。(因为题目只限制不超过K笔交易,但是不能保证交易次数多一定利润大,当只有一天的股票的时候,不交易是利润最大的)

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        const int Min = -0x3f3f3f3f;
        vector<vector<int>> f(n,vector<int>(k+1,Min));
        auto g = f;
        f[0][0] = -prices[0];
        g[0][0] = 0;
        for (int i = 1;i<n;i++)
        {
            for (int j = 0;j<k+1;j++)
            {
                f[i][j] = max(f[i-1][j],g[i-1][j]-prices[i]);
                g[i][j] = g[i-1][j];
                if (j>=1)
                {
                    g[i][j] = max(g[i-1][j],f[i-1][j-1]+prices[i]);
                }
            }
        }
        int ret = g[n-1][0];
        for (int i = 1;i<k+1;i++)
        {
            if (ret<g[n-1][i])
            {
                ret = g[n-1][i];
            }
        }
        return ret;
    }
};

注意:我们上一题两笔交易的时候,要开3个位置,这是因为还要0笔交易也就是不交易的情况,所以这道题给出K笔交易的时候我们还要多加1用来表示第0笔交易。

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

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

相关文章

Delphi XE编写OCX控件

1、new->other 2、Active libary 3、再次New->Other,才出现ActiveX组件内容 设置类名及参数

在 Vue 3 中使用阿里巴巴矢量图标库

在项目中基本会用到图标&#xff0c;比较常见的就是阿里图标库。这篇文章主要介绍如何在vue3中使用图标库。 下载并全局注册自定义图标库 手动下载阿里巴巴矢量图标库的字体文件&#xff1a; 在阿里巴巴矢量图标库网站上选择您需要的图标&#xff0c;并将其添加到购物车。然后…

CMake之CPack

文章目录 一、CPack1.用CPack打包成为deb包2.如何确定的Depends依赖包?3.如何确定编译Build-Depends&#xff1f;4.Cpakc打包RPM包 二、deb的简单使用三、deb包相关文件说明1.control文件2.preinst文件3.postinst文件4.prerm文件5.postrm文件 一、CPack CPack 是 CMake 2.4.2…

SpringBoot 集成 EasyExcel 3.x 实现 Excel 导出

目录 EasyExcel官方文档 EasyExcel是什么&#xff1f; EasyExcel注解 springboot集成EasyExcel 简单入门导出 &#xff1a; 实体类 自定义转换类 测试一下 复杂表头一对多导出 &#xff1a; 自定义注解 定义实体类 自定义单元格合并策略 测试一下 EasyExcel官方文档 …

The Sandbox 展示泰国 2023 年元宇宙生态系统

The Sandbox 举办了 2023 年泰国合作伙伴日活动&#xff0c;宣布创建泰国元宇宙生态系统&#xff0c;并对泰国创客社区的巨大合作和发展表示认可。 The Sandbox 联合创始人兼首席运营官 Sebastien BORGET 说&#xff1a;“我们很高兴见证 The Sandbox 泰国生态系统的发展&#…

第七章 网络安全【计算机网络】

第七章 网络安全【计算机网络】 前言推荐第7章 网络安全7.1网络安全问题概述7.1.1计算机网络面临的安全性威胁7.1.2安全的计算机网络7.1.3数据加密模型 7.2两类密码体制7.2.1对称密钥密码体制7.2.2公钥密码体制 7.3鉴别7.3.1报文鉴别7.3.2实体鉴别 7.4密钥分配7.4.1 对称密钥的…

银行软开能干到退休吗?

大家好&#xff0c;我是熊哥。 21世纪了好像不躺平对不起自己&#xff1f;很多读者都关心哪些企业适合躺平&#xff0c;做程序员是不是在银行可以舒舒服服干一辈子&#xff1f;银行招软开&#xff08;软件开发&#xff09;有哪些要求&#xff1f; 现在就来详细讲一讲。 擦亮…

数据结构--二叉树的线索化

数据结构–二叉树的线索化 用土办法找到中序前驱 typedef struct BiTNode {ElemType data; //数据域struct BiTNode *lchild, *rchild; //左、右孩子指针struct BiTnode *parent; //父节点指针 }BiTNode, *BiTree;BiTNode *p; // p指向目标结点 BiTNode *pre NULL; //指向当前…

用Postman和jmeter做接口测试有什么区别吗?

目录 1.创建接口用例集&#xff08;没区别&#xff09; 2.步骤的实现&#xff08;有区别&#xff09; 3数据用例的实现 4断言的实现 5执行 6其他 总结&#xff1a; 1.创建接口用例集&#xff08;没区别&#xff09; Postman是Collections&#xff0c;Jmeter是线程组&am…

web安全php基础_php之string对象详解

PHP 字符串 字符串变量用于包含有字符的值。 在创建字符串之后&#xff0c;我们就可以对它进行操作了。您可以直接在函数中使用字符串&#xff0c;或者把它存储在变量中。 在下面的实例中&#xff0c;我们创建一个名为 txt 的字符串变量&#xff0c;并赋值为 “Hello world!…

MySQL数据库:数据库管理系统与安装MySQL数据库

目录 一、理论 1.数据库管理系统 2.关系型数据库 3.数据库 4.MySQL数据库 5.MySQL部署 二、实验 1.yum安装MySQL 2.配置MySQL数据库的Tab补全 三、问题 1.数据库登录报错 2.数据库密码复杂度报错 四、总结 一、理论 1.数据库管理系统 &#xff08;1&#xff09…

深入浅出fromCharCode和charCodeAt

先来看一个简单的输入输出 console.log(String.fromCharCode(30328)); //癸console.log(癸.charCodeAt()) //30328来回答一个问题&#xff0c;30328是什么&#xff1f; ascii? unicode? utf-8? utf-16? 来让我们看看fromCharCode 在MDN上面的解释 UTF-16代码单元序列…

【数据分析 - 基础入门之NumPy③】日常难题解决

知识目录 前言一、启动Jupyter Notebook报错没有这样的目录结语# 往期文章&相关导读 前言 本篇文章用于整理在学习 NumPy 过程中遇到的错误&#xff0c;以此做个记录&#xff0c;希望能帮助到大家&#xff0c;让大家少走弯路。 一、启动Jupyter Notebook报错没有这样的目…

改动最小,最简洁的 tomcat catalina.out日志切割(按天)

tomcat日志切割 环境:实现步骤具体操作 环境: 系统: linux操作系统 centOStomcat 8.5* 实现步骤 安装cronolog工具修改tomcat中的bin目录下的 catalina.sh 文件配置重启tomcat 具体操作 安装cronolog 工具输入命令: yum install cronolog 中间 停顿 填个 y 同意修改配置…

什么是cookies,session,token(面试必问)

目录 前言 http无状态 cookies cookie的格式显示 cookie存在问题 利用cookies可以做什么 session session的格式显示 session存在的问题 利用session可以做什么 token token的格式显示 token存在的问题 利用token可以做什么 总结 前言 一般在面试的时候&#xf…

内网隧道代理技术(九)之应用层代理技术介绍

应用层代理技术介绍 前面我们介绍了相关的一些概念和隧道的技术,主要还是停留在单个端口的隧道 1、反弹shell2、端口转发3、端口映射这些都是利用端口建立隧道,但是并不能满足我们日常生活中的要求 加入我们需要对网络中的机器进行扫描,我们就不能使用端口转发或者端口映射…

【计算机视觉 | 目标检测 | 图像分割】arxiv 计算机视觉关于目标检测和图像分割的学术速递(7 月 7 日论文合集)

文章目录 一、检测相关(5篇)1.1 Contextual Affinity Distillation for Image Anomaly Detection1.2 Noise-to-Norm Reconstruction for Industrial Anomaly Detection and Localization1.3 MMNet: Multi-Collaboration and Multi-Supervision Network for Sequential Deepfake…

postman接口测试之postman常用的快捷键

作为一名IT程序猿&#xff0c;不懂一些工具的快捷方式&#xff0c;应该会被鄙视的吧。收集了一些Postman的快捷方式&#xff0c;大家一起动手操作~ 简单操作 操作mac系统windows系统 打开新标签 ⌘TCtrl T关闭标签⌘WCtrl W强制关闭标签Cmd Alt WCtrl Alt W切换到下一个…

【JavaEE】前后端综合项目-博客系统(上)

【JavaEE】前后端综合项目-博客系统&#xff08;上&#xff09; 文章目录 【JavaEE】前后端综合项目-博客系统&#xff08;上&#xff09;1. 创建项目2. 数据库设计3. 数据库操作的封装3.1 DataSource&#xff08;单例&#xff09;3.1 连接操作3.2 关闭操作3.3 创建实体类3.4 封…

“AI无界·智链全球”!壹沓科技2023世界人工智能大会论坛成功举办,构建数智供应链新范式

“AI无界智链全球”论坛成功举办 构建数智供应链新范式 7月6日&#xff0c;由世界人工智能大会组委会办公室指导&#xff0c;壹沓科技主办&#xff0c;上海交通大学安泰经济与管理学院、上海现代服务业联合会物流与供应链专委会、中国航务周刊协办的“AI无界智链全球”高端论坛…