打卡第四十一天:买卖股票的最佳时机

news2024/9/24 13:21:17

一、 买卖股票的最佳时机

题目

文章

视频

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

dp[i][0] 表示第i天持有股票所得最多现金 。其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。dp[i][1] 表示第i天不持有股票所得最多现金

注意这里说的是“持有”,“持有”不代表就是当天“买入”!也有可能是昨天就买入了,今天保持持有的状态

  1. 确定递推公式

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

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

那么dp[i][0]应该选所得现金最大的,所以dp[i][0] = max(dp[i - 1][0], -prices[i]);

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

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]

同样dp[i][1]取最大的,dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);

  1. dp数组如何初始化

由递推公式 dp[i][0] = max(dp[i - 1][0], -prices[i]); 和 dp[i][1] = max(dp[i - 1][1], prices[i] + dp[i - 1][0]);可以看出

其基础都是要从dp[0][0]和dp[0][1]推导出来。

那么dp[0][0]表示第0天持有股票,此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];

dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;

  1. 确定遍历顺序

从递推公式可以看出dp[i]都是由dp[i - 1]推导出来的,那么一定是从前向后遍历。

  1. 举例推导dp数组

以示例1,输入:[7,1,5,3,6,4]为例,dp数组状态如下:

121.买卖股票的最佳时机

dp[5][1]就是最终结果。

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

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

从递推公式可以看出,dp[i]只是依赖于dp[i - 1]的状态。

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

那么我们只需要记录 当前天的dp状态和前一天的dp状态就可以了,可以使用滚动数组来节省空间,代码如下:

// 版本二
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], -prices[i]);
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
        }
        return dp[(len - 1) % 2][1];
    }
};

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

先写出版本一,然后在版本一的基础上优化成版本二,而不是直接就写出版本二。

二、买卖股票的最佳时机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]的时候,第i天买入股票的情况

在上一题中,因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]。而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。

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

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

  • 第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
  • 第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金即:prices[i] + dp[i - 1][0]

注意这里和上一题就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义

代码如下:(注意代码中的注释,标记了唯一不同的地方)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(len, vector<int>(2, 0));
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]); // 注意这里是和121. 买卖股票的最佳时机唯一不同的地方。
            dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + prices[i]);
        }
        return dp[len - 1][1];
    }
};

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

唯一的区别在:

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

这正是因为本题的股票可以买卖多次。所以买入股票的时候,可能会有之前买卖的利润即:dp[i - 1][1],所以dp[i - 1][1] - prices[i]。

滚动数组的版本,C++代码如下:

// 版本二
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int len = prices.size();
        vector<vector<int>> dp(2, vector<int>(2)); // 注意这里只开辟了一个2 * 2大小的二维数组
        dp[0][0] -= prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < len; i++) {
            dp[i % 2][0] = max(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1] - prices[i]);
            dp[i % 2][1] = max(dp[(i - 1) % 2][1], prices[i] + dp[(i - 1) % 2][0]);
        }
        return dp[(len - 1) % 2][1];
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

三、买卖股票的最佳时机III(困难)

题目

文章

视频

关键在于至多买卖两次,这意味着可以买卖一次,可以买卖两次,也可以不买卖。

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

一天一共就有五个状态,

  1. 没有操作 (其实我们也可以不设置这个状态)
  2. 第一次持有股票
  3. 第一次不持有股票
  4. 第二次持有股票
  5. 第二次不持有股票

dp[i][j]中 i表示第i天,j为 [0 - 4] 五个状态,dp[i][j]表示第i天状态j所剩最大现金。

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

例如 dp[i][1] ,并不是说 第i天一定买入股票,有可能 第 i-1天 就买入了,那么 dp[i][1] 延续买入股票的这个状态。

  1. 确定递推公式

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

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

dp[i][1]选 dp[i-1][0] - prices[i],还是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])

同理可推出剩下状态部分:

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

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

  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;

  1. 确定遍历顺序

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

  1. 举例推导dp数组

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

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

现在最大的时候一定是卖出的状态,而两次卖出的状态现金最大一定是最后一次卖出。如果想不明白的录友也可以这么理解:如果第一次卖出已经是最大值了,那么我们可以在当天立刻买入再立刻卖出。所以dp[4][4]已经包含了dp[4][2]的情况。也就是说第二次卖出手里所剩的钱一定是最多的。

所以最终最大利润是dp[4][4]

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

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

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

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

相关文章

【MySQL】数据库约束和多表查询

目录 1.前言 2.数据库约束 2.1约束类型 2.2 NULL约束 2.3 NUIQUE&#xff1a;唯一约束 2.4 DEFAULT&#xff1a;默认值约束 2.5 PRIMARY KEY&#xff1a;主键约束 2.6 FOREIGN KEY&#xff1a;外键约束 1.7 CHECK约束 3.表的设计 3.1一对一 3.2一对多 3.3多对多 …

基于火山引擎云搜索服务和豆包模型搭建 RAG 推理任务

大语言模型&#xff08;LLM&#xff0c;Large language model&#xff09;作为新一轮科技产业革命的战略性技术&#xff0c;其核心能力在于深层语境解析与知识融合。在生成式人工智能方向主要用于图像生成&#xff0c;书写文稿&#xff0c;信息搜索等。当下的 LLM 模型是基于大…

【扒网络架构】backbone、ccff

backbone CCFF 还不知道网络连接方式&#xff0c;只是知道了每一层 backbone backbone.backbone.conv1.weight torch.Size([64, 3, 7, 7])backbone.backbone.layer1.0.conv1.weight torch.Size([64, 64, 1, 1])backbone.backbone.layer1.0.conv2.weight torch.Size([64, 64,…

Vue3封装tabs切换组件

效果如下&#xff1a; 代码如下&#xff1a; <template><div class"tabs-container"><div class"tabs-header"><div v-for"tab in tabs" :key"tab.name" class"tab" click"handleTabClick(tab)&…

TikTok美区账号起号攻略:广告投流和矩阵营销怎么选?

在TikTok美区市场&#xff0c;品牌和卖家可以选择不同的策略来提升曝光率和销售业绩&#xff0c;对于品牌成功至关重要。现在TikTok Shop的两个主流玩法是广告投流和账号矩阵营销&#xff0c;各有其独特优势和适用场景。下面我将对这两种策略综合分析&#xff0c;分别介绍其主要…

告别笨重工具,LICEcap:你的高效GIF录制方案,快来体验!

前言 你是否曾有过这样的烦恼&#xff1a;想要向朋友展示一段精彩的操作&#xff0c;却发现录屏软件要么太过笨重&#xff0c;要么录制的视频文件庞大难以分享&#xff1f;嘿&#xff0c;朋友们&#xff0c;今天小江湖就要给大家安利一款神器——LICEcap&#xff0c;它绝对能让…

速学nvm Nodejs版本管理与nrm源管理

速学nvm Nodejs版本管理与nrm源管理 nvm Nodejs版本管理nvm是什么&#xff1f;nvm安装下载版本&#xff1a;安装步骤&#xff1a;nvm常用指令&#xff1a; nrm源管理nrm是什么&#xff1f;&#xff1f;nrm全局安装nrm常用指令 nvm Nodejs版本管理 NVM 是一种用于管理多个主动节…

自主开发的数据采集监控平台对使用方有什么优势?

蓝鹏测控自主开发的LP-SCADA数据采集监控平台&#xff0c;更具自主性&#xff0c;完全可以根据使用方的需求进行系统定制&#xff0c;如&#xff1a;功能、显示、界面、采集数量、数据分析等。 自主开发 基于WEB的一套数据采集系统平台&#xff0c;同各种设备及三方系统建立通…

用LLM搭建100个应用:从0到1搭建自己的Windows贾维斯

从ChatGPT发布至今&#xff0c;确实所有的应用都值得用大模型重新做一遍。国内外对基底大模型卷了又卷&#xff0c;新生的应用也在模型的迭代过程中&#xff0c;起起伏伏。 但可以坚信的是&#xff0c;AGI的方向和每个时代人们永远在变的不变的需求。 而求外不如求己&#xff0…

人像后期精修 调色+精修笔记(精修+化妆+美术)

色彩调节 关于调色曲线的学习&#xff1a; 学习链接&#xff1a;一看就懂的曲线调色教程【手机摄影后期】_哔哩哔哩_bilibili 从左向右就是由暗部越来越到亮部 越靠近右侧的越是亮部 精修常用技巧 学习视频&#xff1a;【PS精修教程】2024最详细最全的PS人像精修全套68集&a…

机器学习入门(五):K近邻算法API K值选择问题

目录 1. K 近邻算法 API 1.1 Sklearn API介绍 1.2 鸢尾花分类示例代码 1.3 小结 2. K 值选择问题 2.1 K取不同值时带来的影响 2.2 如何确定合适的K值 2.3 GridSearchCV 的用法 2.4 小结 1. K 近邻算法 API K近邻&#xff08;K-Nearest Neighbors, KNN&#xff09;算法作…

(附源码)SSM动漫展示系统的开发-计算机毕设 25454

SSM动漫展示系统的开发 摘 要 21世纪&#xff0c;全球网络化&#xff0c;科技在突飞猛进。我们的生活也随之发生了极大的变化。随着计算机的普及&#xff0c;我们社会和经济生活中的各个领域也在发生改变。人们进行信息交流的深度与广度在不断增加,这使得传统的行业模式也要跟随…

【乐吾乐大屏可视化组态编辑器】发送指令

发送指令 在线使用&#xff1a;https://v.le5le.com/ 发送指令是指将数据通过通信接口下发到设备 1. 拖动图元&#xff08;以按钮为例&#xff09;到画布&#xff0c;右侧切换到交互面板&#xff0c;添加单击事件。 2. 点击“添加动作”&#xff0c;动作类型选择“发送数据”…

图像文本擦除无痕迹!复旦提出EAFormer:最新场景文本分割新SOTA!(ECCV`24)

文章链接&#xff1a;https://arxiv.org/pdf/2407.17020 git链接&#xff1a;https://hyangyu.github.io/EAFormer/ 亮点直击 为了在文本边缘区域实现更好的分割性能&#xff0c;本文提出了边缘感知Transformer&#xff08;EAFormer&#xff09;&#xff0c;该方法明确预测文…

JS 【详解】sourcemap

sourcemap 的作用 JS 上线时要压缩、混淆&#xff0c;线上的 JS 报错信息无法识别行、列&#xff0c;sourcemap 可解决这个问题 sourcemap 的原理 sourcemap 文件中&#xff0c;保存了 JS 代码压缩后和压缩前的对应关系 怎样找到 sourcemap 文件 方法1&#xff1a;将 JS 的后缀…

以太彩光网 VS PON网络 谁更适合企业级园区

耿望阳 中建协绿建与智能分会专家委副主任、华南理工大学建筑设计研究院电气(智能化)顾问总工 光进铜退的背后,折射的是时代的变迁,技术的进步。自2009年起,光纤技术至今早已潜移默化渗透到人们工作和生活每个角落,全光网已经具备了未来的确定性,而对于企业级市场来说,该怎么…

c语言第12天

指针的引入 为函数修改实参提供支持。 为动态内存管理提供支持。 为动态数据结构提供支持。 为内存访问提供另一种途径。 指针概述 内存地址&#xff1a;系统为了内存管理的方便&#xff0c;将内存划分为一个个的内存单元&#xff08;1个内存单元占1个字 节&#xff09;&…

opencv 深度图视差图可视化案例

参考:https://www.cnblogs.com/zyly/p/9373991.html(图片这里面下载的) https://blog.csdn.net/He3he3he/article/details/101053457 双目测距论文: http://www.shcas.net/jsjyup/pdf/2016/9/%E5%9F%BA%E4%BA%8E%E5%8F%8C%E7%9B%AE%E7%AB%8B%E4%BD%93%E8%A7%86%E8%A7%89%E…

【51蛋骗鸡矩阵键盘组合键的使用】2021-12-28

组合键以第一按键所在的行列除外可以和任意的按键组合&#xff0c;每一个都可以和剩下的9个组合。 unsigned char JianPanShaoMiao(/*使用行列反转扫描法*/) { unsigned char H15,L240,Ys0;P1H;if(P1!15){ while(Ys);//消抖HP1;P1L;LP1;while(Ys);//消抖 // while(P1!240);/…

Temu测评自养号的基本概念和目的

在跨境电商领域&#xff0c;自养号的创建与维护已成为提升业务效率、规避平台风险的关键策略。实现稳定、高效、安全的Temu测评自养号运营。 环境系统构建&#xff1a;掌握核心技术&#xff0c;规避依赖风险 市场上的现成解决方案往往缺乏定制化风控能力&#xff0c;自建系统则…