打开第四十二天:买卖股票的最佳时机IV、最佳买卖股票时机含冷冻期、买卖股票的最佳时机含手续费

news2024/11/25 22:24:42

一、买卖股票的最佳时机IV(困难)

题目

文章

视频

这道题目可以说是上一题的进阶版,这里要求至多有k次交易。

  1. 确定dp数组以及下标的含义

在上一题中定义了一个二维dp数组,本题其实依然可以用一个二维dp数组。使用二维数组 dp[i][j] :第i天的状态为j,所剩下的最大现金是dp[i][j]

j的状态表示为:

  • 0 表示不操作
  • 1 第一次买入
  • 2 第一次卖出
  • 3 第二次买入
  • 4 第二次卖出
  • .....

除了0以外,偶数就是卖出,奇数就是买入

题目要求是至多有K笔交易,那么j的范围就定义为 2 * k + 1 就可以了。

所以二维dp数组的C++定义为:

vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));

  1. 确定递推公式

dp[i][1],表示的是第i天,买入股票的状态,并不是说一定要第i天买入股票

达到dp[i][1]状态,有两个具体操作:

  • 操作一:第i天买入股票了,那么dp[i][1] = dp[i - 1][0] - prices[i]
  • 操作二:第i天没有操作,而是沿用前一天买入的状态,即:dp[i][1] = dp[i - 1][1]

选最大的,所以 dp[i][1] = max(dp[i - 1][0] - prices[i], dp[i - 1][1]);

同理dp[i][2]也有两个操作:

  • 操作一:第i天卖出股票了,那么dp[i][2] = dp[i - 1][1] + prices[i]
  • 操作二:第i天没有操作,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i - 1][2]

所以dp[i][2] = max(dp[i - 1][1] + prices[i], dp[i - 1][2])

同理可以类比剩下的状态,代码如下:

for (int j = 0; j < 2 * k - 1; j += 2) {
    dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
    dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
}

本题和上一题最大的区别就是这里要类比j为奇数是买,偶数是卖的状态

  1. dp数组如何初始化

第0天没有操作,这个最容易想到,就是0,即:dp[0][0] = 0;

第0天做第一次买入的操作,dp[0][1] = -prices[0];当天买入,当天卖出,所以dp[0][2] = 0;

第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后在买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。

所以第二次买入操作,初始化为:dp[0][3] = -prices[0];

第二次卖出初始化dp[0][4] = 0;

所以同理可以推出dp[0][j]当j为奇数的时候都初始化为 -prices[0]

代码如下:

for (int j = 1; j < 2 * k; j += 2) {
    dp[0][j] = -prices[0];
}

在初始化的地方同样要类比j为偶数是卖、奇数是买的状态

  1. 确定遍历顺序

从递归公式其实已经可以看出,一定是从前向后遍历,因为dp[i],依靠dp[i - 1]的数值。

  1. 举例推导dp数组

以输入[1,2,3,4,5],k=2为例。

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

最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解。

以上分析完毕,C++代码如下:

class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {

        if (prices.size() == 0) return 0;
        vector<vector<int>> dp(prices.size(), vector<int>(2 * k + 1, 0));
        for (int j = 1; j < 2 * k; j += 2) {
            dp[0][j] = -prices[0];
        }
        for (int i = 1;i < prices.size(); i++) {
            for (int j = 0; j < 2 * k - 1; j += 2) {
                dp[i][j + 1] = max(dp[i - 1][j + 1], dp[i - 1][j] - prices[i]);
                dp[i][j + 2] = max(dp[i - 1][j + 2], dp[i - 1][j + 1] + prices[i]);
            }
        }
        return dp[prices.size() - 1][2 * k];
    }
};

  • 时间复杂度: O(n * k),其中 n 为 prices 的长度
  • 空间复杂度: O(n * k)

二、最佳买卖股票时机含冷冻期

题目

文章

视频

本题加上了一个冷冻期

  1. 确定dp数组以及下标的含义

dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。

今天买入股票、今天卖出股票、今天是冷冻期,都是不能操作股票的。

具体可以区分出如下四个状态:

  • 状态一:持有股票状态(今天买入股票,或者是之前就买入了股票然后没有操作,一直持有)
  • 不持有股票状态,这里就有两种卖出股票状态
    • 状态二:保持卖出股票的状态(两天前就卖出了股票,度过一天冷冻期。或者是前一天就是卖出股票状态,一直没操作)
    • 状态三:今天卖出股票
  • 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天

j的状态为:

  • 0:状态一
  • 1:状态二
  • 2:状态三
  • 3:状态四

本题有冷冻期,而冷冻期的前一天,只能是 「今天卖出股票」状态,如果是 「不持有股票状态」那么就很模糊,因为不一定是 卖出股票的操作。

注意这里的每一个状态,例如状态一,是持有股票股票状态并不是说今天一定就买入股票,而是说保持买入股票的状态即:可能是前几天买入的,之后一直没操作,所以保持买入股票的状态

  1. 确定递推公式

达到买入股票状态(状态一)即:dp[i][0],有两个具体操作:

  • 操作一:前一天就是持有股票状态(状态一),dp[i][0] = dp[i - 1][0]
  • 操作二:今天买入了,有两种情况
    • 前一天是冷冻期(状态四),dp[i - 1][3] - prices[i]
    • 前一天是保持卖出股票的状态(状态二),dp[i - 1][1] - prices[i]

那么dp[i][0] = max(dp[i - 1][0], dp[i - 1][3] - prices[i], dp[i - 1][1] - prices[i]);

达到保持卖出股票状态(状态二)即:dp[i][1],有两个具体操作:

  • 操作一:前一天就是状态二
  • 操作二:前一天是冷冻期(状态四)

dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);

达到今天就卖出股票状态(状态三),即:dp[i][2] ,只有一个操作:

昨天一定是持有股票状态(状态一),今天卖出

即:dp[i][2] = dp[i - 1][0] + prices[i];

达到冷冻期状态(状态四),即:dp[i][3],只有一个操作:

昨天卖出了股票(状态三)

dp[i][3] = dp[i - 1][2];

综上分析,递推代码如下:

dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
dp[i][2] = dp[i - 1][0] + prices[i];
dp[i][3] = dp[i - 1][2];

  1. dp数组如何初始化

这里主要讨论一下第0天如何初始化。

如果是持有股票状态(状态一)那么:dp[0][0] = -prices[0],一定是当天买入股票。

保持卖出股票状态(状态二),这里其实从 「状态二」的定义来说 ,很难明确应该初始多少,这种情况就看递推公式需要我们给他初始成什么数值。

如果i为1,第1天买入股票,那么递归公式中需要计算 dp[i - 1][1] - prices[i] ,即 dp[0][1] - prices[1],那么大家感受一下 dp[0][1] (即第0天的状态二)应该初始成多少,只能初始为0。想一想如果初始为其他数值,是我们第1天买入股票后 手里还剩的现金数量是不是就不对了。

今天卖出了股票(状态三),同上分析,dp[0][2]初始化为0,dp[0][3]也初始为0。

  1. 确定遍历顺序

从递归公式上可以看出,dp[i] 依赖于 dp[i-1],所以是从前向后遍历。

  1. 举例推导dp数组

以 [1,2,3,0,2] 为例,dp数组如下:

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

最后结果是取 状态二,状态三,和状态四的最大值

代码如下:

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

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

三、买卖股票的最佳时机含手续费

题目

文章

视频

相对于最佳时机II,本题只需要在计算卖出操作的时候减去手续费就可以了,代码几乎是一样的。唯一差别在于递推公式部分

重申一下dp数组的含义:dp[i][0] 表示第i天持有股票所省最多现金。 dp[i][1] 表示第i天不持有股票所得最多现金

如果第i天持有股票即dp[i][0], 那么可以由两个状态推出来

  • 第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
  • 第i天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]

所以:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);

在来看看如果第i天不持有股票即dp[i][1]的情况, 依然可以由两个状态推出来

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金,注意这里需要有手续费了即:dp[i - 1][0] + prices[i] - fee

所以:dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee);

本题和II的区别就是这里需要多一个减去手续费的操作

以上分析完毕,C++代码如下:

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

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

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

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

相关文章

wireshark使用介绍及案例分享

一、wireshark介绍 1、定义 wireshark是非常流行的网络封包分析软件,简称小鲨鱼,功能十分强大。可以截取各种网络封包,显示网络封包的详细信息。对应的,linux下的抓包工具是 tcpdump。 1.1、网络基础 参考TCP/IP五层模型,帧结构如下: 帧字段 帧字段含义 Frame 物理层的…

百元蓝牙耳机哪个牌子的比较好?四款百元必入热门机型盘点

一款优秀的蓝牙耳机都能极大提升我们的使用体验&#xff0c;然而&#xff0c;对于大多数消费者而言&#xff0c;高端蓝牙耳机昂贵的价格常常令人望而却步&#xff0c;幸运的是&#xff0c;市场上有很多性价比极高的品牌提供了百元左右的优质选择&#xff0c;那么百元蓝牙耳机哪…

基于STM32开发的智能电能监测系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 初始化代码控制代码应用场景 家庭电能监测工业用电管理常见问题及解决方案 常见问题解决方案结论 1. 引言 智能电能监测系统通过实时采集电流、电压等电力参数&#xff0c;计算电能消耗&…

FPGA开发——UART串口通信的介绍和回环实验框架构建

一、简介 1、原理 UART&#xff08;Universal Asynchronous Receiver/Transmitter&#xff0c;通用异步收发器&#xff09;是一种广泛使用的串行通信协议&#xff0c;特别适用于微控制器、计算机和各种嵌入式设备之间的数据通信。 UART是一种异步串行通信方式&#xff0c;通过…

Prometheus+Grafana-1-基础介绍及安装

一、体系架构(了解) 数据采集流程 说白了就是采集数据->计算是否超过阈值->发起警告 Prometheus查询界面如下 1.报警简介 展现形式&#xff1a;短信&#xff0c;邮件&#xff0c;电话&#xff0c;通讯软件。 阈值(Trigger Value)&#xff0c;如达到阈值可以触发预警。…

巨详细的规则引擎 Drools——小白也可食用

巨详细的规则引擎 Drools——小白也可食用 一、问题1.1、传统做法1.2、存在的问题1.3、引入 二、规则引擎概述2.1、什么是规则引擎2.2、使用规则引擎的优势2.3、规则引擎应用场景2.4、Drools介绍 三、Drools入门案例3.1、创建Springboot项目3.2、引入依赖3.3、添加Drools配置类…

鸿蒙(API 12 Beta3版)【投播组件开发指导】分布式媒体会话

通过本开发指导&#xff0c;完成一次音视频跨设备投播。 约束与限制 需同时满足以下条件&#xff0c;才能使用该功能&#xff1a; 设备限制 本端设备&#xff1a;HarmonyOS NEXT Developer Preview0及以上版本的手机设备 远端设备&#xff1a;HarmonyOS NEXT Developer Previ…

动起来!Axure动画的实用技巧

前言 在数字产品的世界中&#xff0c;用户体验的每一个细节都至关重要。 动画&#xff0c;作为一种增强交互性和视觉吸引力的手段&#xff0c;已经成为现代界面设计中不可或缺的元素。 Axure&#xff0c;作为一款强大的原型设计工具&#xff0c;提供了丰富的动画功能&#x…

进程第二部分

1.任务&#xff1a;子进程做的事情和父进程差不多&#xff08;子承父业&#xff09; 父进程创建出子进程之后&#xff0c;子进程做的事情与父进程完全不同&#xff08;自力更生&#xff09; 2.exec: int exec l(const char *path, const char *arg, ...); int exec v(const c…

python管理mysql

[rootserver1 ~]# pip3 config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple //这是设置清华镜像站&#xff0c;提高下载安装速度&#xff0c;如果报错提示找不到config&#xff0c;这是因为版本低&#xff0c;临时使用清华镜像站来升级 pip&…

愤怒的江小白,这几年怎么了?

近日&#xff0c;东方甄选和江小白的纠纷引发了广泛关注。 8月8日晚间&#xff0c;东方甄选直播间中&#xff0c;主播天权在带货某款白酒产品时突然对比点评江小白产品&#xff0c;称江小白“不是白酒”&#xff1a;“你现在去看江小白的包装&#xff0c;上面是没有‘白酒’这…

1Panel 部署 爱影CMS

前言 本文以 爱影CMS 为例提供了一种在 1Panel 面板中在 docker 内运行二进制程序的一种思路。符合 1Panel 基于容器管理并部署应用&#xff0c;实现最小的漏洞暴露面的思路。 环境准备 完整的 1Panel 环境&#xff08;1Panel 如何安装&#xff1f; / 1Panel Pro 优惠购)服务…

软件测试学习搭子抱团取暖(已有400+)

希望优秀的测试人都聚齐在一起&#xff0c;大家一起解决面试难题&#xff0c;工作上的问题&#xff0c;在解决问题的同时自己也能提升&#xff0c;同时相互交流分享资料等测试面试经验&#xff0c;一起讨论交流呀。定个小目标2000。

除了知云文献翻译外,这几款翻译工具值得推荐!

近年来&#xff0c;市面上涌现出众多优秀的文献翻译工具&#xff0c;其中知云文献翻译凭借其强大的功能受到了广泛好评。然而&#xff0c;除了知云文献翻译外&#xff0c;还有几款翻译工具同样值得推荐。今天&#xff0c;就让我们一起来了解一下&#xff01; Foxit在线翻译 链…

【Qt】控件介绍

控件概念 一个图形化界面上的内容&#xff0c;在编写代码的时候不需要全部从零开始实现。Qt种已经提供了很多内置的控件&#xff08;按钮、文本框、单选按钮、复选按钮、下拉框等等&#xff09;&#xff0c;直接进行使用即可。 控件&#xff0c;英文名称widget&#xff0c;控件…

企业源代码加密软件有哪些?2024最好用的十款源代码加密软件

在当今快速发展的技术环境中&#xff0c;企业源代码的安全性至关重要。源代码不仅包含着企业的核心知识产权&#xff0c;还可能涉及敏感的商业数据。因此&#xff0c;选择一款合适的源代码加密软件&#xff0c;对于保护企业资产和避免数据泄露风险至关重要。随着安全技术的不断…

(回溯) LeetCode 47. 全排列||

原题链接 建议先练习&#xff1a;全排列| 一. 题目描述 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2] 输出&#xff1a; [[1,1,2],[1,2,1],[2,1,1]]示例 2&#xff1a; 输入&a…

【JavaEE初阶】线程池

目录 &#x1f4d5; 引言 &#x1f333; 概念 &#x1f340;ThreadPoolExecutor 类 &#x1f6a9; int corePoolSize与int maximumPoolSize&#xff1a; &#x1f6a9; long keepAliveTime与TimeUnit nuit&#xff1a; &#x1f6a9; BlockingQueue workQueue&#xff1a…

C# 序列化与反序列化指南:将对象数据写入/读取到XML文件

文章目录 1. XML 文件的基本概念以及为何使用 XML 文件进行数据序列化2. C# 中的 XML 文件序列化使用 XmlSerializer 类进行操作的详细步骤3. 创建一个自定义对象并序列化对象数据到 XML 文件的示例代码4. 读取 XML 文件并反序列化&#xff08;将 XML 数据转换为对象数据&#…

【Redis】Redis 初探:特性、应用场景与高并发架构演进之路

目录 初识 Redis关于 Redis服务端高并发分布式结构演进之路概述常⻅概念基本概念应⽤&#xff08;Application&#xff09;/ 系统&#xff08;System&#xff09;模块&#xff08;Module&#xff09;/ 组件&#xff08;Component&#xff09;分布式&#xff08;Distributed&…