代码随想录第49天

news2024/12/23 13:24:28

1.买卖股票的最佳时机:

贪心

因为股票就买卖一次,那么贪心的想法很自然就是取最左最小值,取最右最大值,那么得到的差值就是最大利润。

C++代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int low = INT_MAX;
        int result = 0;
        for (int i = 0; i < prices.size(); i++) {
            low = min(low, prices[i]);  // 取最左最小价格
            result = max(result, prices[i] - low); // 直接取最大区间利润
        }
        return result;
    }
};

动态规划:

动规五部曲分析如下:

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

dp[i][0] 表示第i天持有股票所得最多现金 ,这里可能有同学疑惑,本题中只能买卖一次,持有股票之后哪还有现金呢?

其实一开始现金是0,那么加入第i天买入股票现金就是 -prices[i], 这是一个负数。

dp[i][1] 表示第i天不持有股票所得最多现金

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

很多同学把“持有”和“买入”没区分清楚。

在下面递推公式分析中,我会进一步讲解。

     2.确定递推公式

如果第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]);

这里dp[i][0]都是负的,所以越大越好(也就是越接近0),这样买股票的成本才小。其实就是在找股票成本最小的,只不过他是所得现金,所以是负的,所以变成了找最大的

如果第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]);

这里就是利润了,越大越好。

这样递推公式我们就分析完了

      3.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;

      4.确定遍历顺序

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

      5.举例推导dp数组

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

其实我还有一种思路举例dp数组和他的一模一样(只不过表是横过来的,dp[i][0]都加一个绝对值):

对应题解的举例dp数组dp[i][0]是股票的最小值,dp[i][1]是最大利润。(我感觉和贪心的思路一样)

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

为什么不是dp[5][0]呢?

因为本题中不持有股票状态所得金钱一定比持有股票状态得到的多!

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

// 版本一
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];
    }
};

 还有一种滚动数组的写法:

// 版本二
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];
    }
};

2.买卖股票的最佳时机II

首先明确一点如果你前面买了股票了他那个股票可以在同一天卖掉并买这一天的股票:

[1,2,3]的最大利润是2不是1.

所以只要把从第2天开始的正利润加起来就是最大值。

和买卖股票的最佳时机I几乎一模一样,就是在递推公式上有点小差别:

因为它可以买卖股票好多次,所以当他在第i天买入股票时,递推公式是dp[i - 1][1] - prices[i],原来的递推公式是 -prices[i],因为他就买卖一次找最大差值就行了,而这里是把每天的正利润加起来,所以要加上前面的已经赚到的利润。

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]

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

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

注意这里和121. 买卖股票的最佳时机 (opens new window)就是一样的逻辑,卖出股票收获利润(可能是负值)天经地义!

为什么求正利润之和,卖出股票的利润是负的?

其实这边是有点疑问的,尤其是看完贪心的思路,求正利润之和,但是其实是不用担心的,因为

他是无论当下这个股票比前面的大还是小都计算了:prices[i] + dp[i - 1][0],但是根本不会出现这种情况因为在没出现正利润之前,也就是负利润时,初始化的0比负利润大,所以利润还是0,直到出现正利润。出现正利润之后就是max(dp[i-1][1],dp[i-1][0]+prices[i])这两个的比较了,不要把前面的dp[i-1][1]写成0,不然一旦出现正利润之后就都是dp[i-1][0]+prices[i]。

滚动数组的写法:

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];
    }
};

 

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

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

相关文章

一键部署个人ChatGPT Web网站

一键部署个人ChatGPT Web网站 githubVercel使用自己的域名 本文将向大家介绍如何通过Github和Vercel这两个具,轻松搭建自己的ChatGPT Web网站&#xff0c;并且我们还可以添加密码保护以防止恶意滥用。 github 首先&#xff0c;我们需要拥有一个Github账号和Vercel账&#xff0…

【Set集合】概述和特点

Set集合概述和特点 Set集合概述 Set作为Collection集合的子接口&#xff0c;没有新增的功能&#xff0c;Collection集合可用的功能Set集合也可以用。 Set集合特点 Set集合存储的元素是无序的&#xff0c;而且不允许存储重复的元素&#xff0c;每当有新的元素存入的时候&#x…

操作系统复习3.2.1-虚拟内存

传统存储的问题 一次性&#xff1a;一次性装入作业才能运行 驻留性&#xff1a;不是所有部分都需要长时间存放在内存中 定义和特征 将要用的部分载入内存&#xff0c;不用的部分调出外存&#xff0c;逻辑上扩大内存 虚拟内存的最大容量为计算机的寻址范围决定 实际容量则为内…

【哈希值】概述和特点

哈希值概述和特点 哈希值概述 哈希值是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值 Object类中有一个方法可以获取对象的哈希值&#xff1a; public int hashCode()&#xff1a;返回对象的哈希值 哈希值特点 同一个对象多次调用hashCode()方法返回的哈希值是相…

0x36transferData 数据传输

0x36transferData TransferData服务用于客户端将数据从客户端传输到服务器&#xff08;下载&#xff09;或从服务器传输到客户端&#xff08;上传&#xff09; 。 数据传输方向由前面的RequestDownload或RequestUpload服务定义。 如果客户端发起请求下载&#xff0c;则要下载的…

【01】STM32·HAL库开发-单片机简介 |用处、发展历程、发展趋势、CISC与RISC对比、冯诺依曼和哈佛结构对比

目录 1.单片机是什么&#xff08;了解&#xff09;2.单片机有什么用&#xff08;了解&#xff09;3.单片机发展历程&#xff08;了解&#xff09;4.单片机发展趋势&#xff08;了解&#xff09;5.CISC & RISC&#xff08;了解&#xff09;5.1CISC和RISC举例5.2冯诺依曼结构…

【C++】位图应用 | 布隆过滤器

文章目录 1. 位图应用题目一代码实现setrsettest具体代码 题目二位图优缺点总结 2. 布隆过滤器提出背景概念具体实现hash1 hash2 hash3N取值问题settsettset中在与不在那个准确&#xff1f;使用场景及特点具体代码 1. 位图应用 题目一 给40亿个不重复的无符号整数&#xff0c…

压缩感知重构之凸松弛法

算法的重构是压缩感知中重要的一步&#xff0c;是压缩感知的关键之处。因为重构算法关系着信号能否精确重建&#xff0c;国内外的研究学者致力于压缩感知的信号重建&#xff0c;并且取得了很大的进展&#xff0c;提出了很多的重构算法&#xff0c;每种算法都各有自己的优缺点&a…

Linux-地址空间

文章目录 问题引入操作系统宏观认识操作系统与进程程序地址空间进程地址空间问题解释 问题引入 在Linux操作系统中、vim编译器下&#xff0c;出现了变量同地址但不同值的现象。 下面以解释该现象产生的原因为主线&#xff0c;在过程中学习Linux操作系统的知识。 运行代码展示…

chatgpt赋能python:Python分词处理

Python分词处理 随着网络技术的飞速发展&#xff0c;搜索引擎已成为人们了解信息的主要渠道之一&#xff0c;而搜索引擎的核心是关键词匹配&#xff0c;因此分词技术在搜索引擎优化&#xff08;SEO&#xff09;中起着至关重要的作用。Python作为一种强大的编程语言&#xff0c…

【学习日记2023.6.3】之 工作台显示和报表导出

文章目录 12. 工作台显示和报表导出12.1 工作台12.1.1 需求分析和设计12.1.2 代码开发Controller层Service层接口Service层实现类Mapper层 12.1.3 功能测试12.1.4 提交代码 12.2 Apache POI12.2.1 介绍12.2.2 入门案例12.2.2.1 将数据写入Excel文件12.2.2.2 读取Excel文件中的数…

Linux4.7Nginx优化与防盗链

文章目录 计算机系统5G云计算第六章 LINUX Nginx优化与防盗链一、Nginx服务优化和深入优化1.隐藏版本号2.修改用户与组3.缓存时间4.日志切割5.连接超时6.更改进程数7.配置网页压缩8.配置防盗链9.fpm参数优化 计算机系统 5G云计算 第六章 LINUX Nginx优化与防盗链 一、Nginx服…

操作系统复习4.1.0-文件管理结构

定义 一组有意义的信息的集合 属性 文件名、标识符、类型、位置、大小、创建时间、上次修改时间、文件所有者信息、保护信息 操作系统向上提供的功能 创建文件、删除文件、读文件、写文件、打开文件、关闭文件 这6个都是系统调用 创建文件 创建文件时调用Create系统调用…

django中使用celery

Celery介绍&#xff1a; 核心及优点&#xff1a;1.基于分布式系统架构&#xff08;负载均衡避免单点故障&#xff0c;高可用&#xff09; 2.实现了异步任务的调度&#xff08;快速&#xff09; 只需要通过配置文件的修改就可以实现架构的切换所以灵活 django-celery-beat 用…

Oracle中的循环

目录 一、简单循环 1.1LOOP 循环语法&#xff1a; 1.2LOOP 循环示例 二、for循环 2.1for循环语法&#xff1a; 2.2for循环示例 三、while循环 3.1while循环语法 3.2while循环示例 四、GOTO 循环 4.1GOTO 循环语法 4.2GOTO 循环示例 在 Oracle 数据库中&#xff0c;…

储能之动力电池与储能电池区别?

储能之动力电池与储能电池区别 1、概念1.1 动力电池1.2 储能电池 2、应用场景3、动力电池与储能电池的对比3.1 性能要求3.2 循环次数3.3 电池类型方面3.4 成本结构不同 1、概念 1.1 动力电池 动力电池即为工具提供动力来源的电源&#xff0c;多指为电动汽车、电动列车、电动自…

Oracle中ORA-12560:协议适配器错误

平时在长时间未登录Oracle数据库&#xff0c;再次登录时会出现如下错误&#xff1a; 当Oracle登录时出现12560协议适配器错误时&#xff0c;可以通过以下步骤尝试启动相应的服务&#xff1a; 第一步&#xff1a; 打开本地【服务】&#xff0c;点击最顶层的名称输入【O】&…

java-字符流和字节流(三)

java-字符流和字节流(三) 一、IO特殊操作流 1.1 标准流 1.1.1 标准输入流 System类中有两个静态的成员变量 public static final InputStream in&#xff1a;标准输入流。通常该流对应于键盘输入或由主机环境或用户指定的另一个输入源public static final PrintStream out&am…

【StringBuilder类】添加和反转方法以及StringBuilder和String相互转换

StringBuilder类 如果对字符串进行拼接操作&#xff0c;每次拼接都会构建一个新的String对象&#xff0c;既耗时又浪费内存空间&#xff0c;而这种操作还不可避免。我们可以通过Java提供的StringBuilder类来解决这个问题。StringBuilder是一个可变的字符串类&#xff0c;我们可…

java-基础语法(一)

java-基础语法(一) 一、java变量 1.1、注释 单行注释 // // 这是单行注释文字多行注释 /* *//* 这是多行注释文字 这是多行注释文字 这是多行注释文字 */ 注意&#xff1a;多行注释不能嵌套使用。1.2 常量 常量&#xff1a;在程序运行过程中&#xff0c;其值不可以发生改变的…