概述

熟悉这类题目的同学都知道,事实上这是一类动态规划(dp)问题,在小专题中都暂且只针对这一类问题进行解决了,更全面的动态规划文章以后有机会再努力吧!

简单介绍一句动态规划。动态规划实际是一种分治思想,与分治算法不同的是,分治算法是把原问题分解为若干子问题,自顶向下求解各子问题,合并子问题的解,从而得到原问题的解。动态规划也是把原问题分解为若干子问题,然后自底向上,先求解最小的子问题,把结果存储在表格中,再求解大的子问题时,直接从表格中查询小的子问题的解,避免重复计算,从而提高算法效率

对于本类问题而言,我们采用有限状态自动机的思想来解决,实际就是找到所有状态,并根据状态转移图来列状态转移方程,实际还是dp的思想,下面我们以第一道题为例来介绍。

例题

我们以121.买卖股票的最佳时机为例来进行讲解。题目如下:

题目

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

示例 :

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

分析

首先我们归纳一下题意:每天股票价格在数组prices中,每天可以选择买入、卖出或是什么都不做。只有没有股票时才能买入,只有有股票时才能卖出,而且只能买入(卖出)一次,求最大利润。

下面我们需要明确几件事:

  • 整个股票买卖过程可以进行哪些操作(选择)?每一天都可以买入、卖出、休息(既不买入也不卖出),只不过买入和卖出是有要求的,必须先买入才能卖出。
  • 既然采用自动机来解决,那么过程中有那些状态呢?因为可以进行以上三种操作,因此每一天有两种状态:持有股票不持有股票。今天持有股票是因为之前或今天买入了股票且没有卖出;今天不持有股票是因为之前或今天一直没买股票或者已经卖出了。
  • 最大利润在哪里产生?一定是在最后一天不持有股票状态。(显然把股票卖掉一定比手里拿着股票获利更多)

我们根据是否持有股票这两个状态画出如下图所示的状态转移图。图中状态0表示当前不持有股票,状态1表示当前持有股票

对上图的转移过程的解释如下:当我们不持有股票时,可以通过买入buy变为持有状态;也可以休息rest仍然为不持有状态。当我们持有股票时,可以通过卖出sale变为不持有状态;也可以休息rest仍然为持有状态。

有了状态转移图,我们应该考虑一种数据结构来记录每个时刻(每天)的状态(股票的持有状态)。因此使用一个二维数组来记录,记为dp数组。举例:dp[i][0]表示第i天时不持有股票时的最大利润,dp[i][1]表示第i天时持有股票的最大利润。然后写出状态转移方程,也就是上述两个式子是怎么由其他状态转换来的,怎么求出来的。

dp[i][0]为例,什么情况下第i天才能不持有股票呢?无非就是两种情况:

  • i-1天也是不持有股票,然后第i天也没有买入,此时利润仍然等于第i-1天的最大利润;
  • i-1天持有了股票,但第i天进行卖出了,此时利润等于第i-1天持有股票的利润加上卖出股票的获利。(因为股票只能买卖一次,因此第i-1天持有股票的利润就是之前买股票的花费)

因此dp[i][0]应该取以上两种情况的最大值,即dp[i][0]=max(dp[i-1][0],dp[i-1][0]+prices[i])

同理,以下两种情况下第i天会持有股票:

  • i-1天就已经持有股票,然后第i天没有卖出,此时利润仍然等于第i-1天的最大利润;
  • i-1天不持有股票,但第i天买入了股票,此时利润等于买入股票的价格的相反数。(因为股票只能买卖一次,因此第i天买入股票的利润就是当天买入股票的花费)

因此,dp[i][1]=max(dp[i-1][1],-prices[i])

这样我们就已经写出状态转移方程了,下面要思考初始的状态(初态,显然方程中i不能为0,因此我们把i=0时的情况分析出来,剩下的就从i=1开始直接递推了)。

  • dp[0][0]=0:第1天不持有股票,因此利润是0
  • dp[0][1]=prices[0]:第1天持有股票,即第一天买了股票,利润是第一天股票价格的相反数

参考代码

class Solution {public int maxProfit(int[] prices) {int [][]dp=new int[prices.length][2];dp[0][0]=0;// 第1天不持有股票,利润为0dp[0][1]=-prices[0];// 第1天持有股票,利润为第一天股票售价的相反数for(int i=1;i<prices.length;i++){// 第i天如果没持有股票,对应两种情况:1.前一天就是没有持有股票的状态2.今天把前一天持有的股票给卖了,额外获利今天的股票价格。dp[i][0]=Math.max(dp[i-1][0],dp[i-1][1]+prices[i]);// 第i天如果持有股票,对应两种情况:1.前一天就是持有股票的状态2.今天刚刚买入股票,额外加上今天股票价格的支出。dp[i][1]=Math.max(dp[i-1][1],-prices[i]);}return dp[prices.length-1][0];// 最大利润产生在【最后一天的不持有股票状态】}
}

代码优化

观察上面的代码我们发现,事实上每一天的利润只与上一天的利润有关,最后需要的答案也只是最后一天的不持有股票状态,因此完全可以不用dp数组记录每天的每个状态,只需要用变量来记录上一天的每个状态就可以了,这样能使空间复杂度降为O(1)。核心思想是不变的,只是本题并不需要记录所有状态,只需要保证有足够的数据(前一天的状态数据)来递推出最后一天的情况即可。

即:使用变量dp0来代替dp数组中每天不持有股票的情况dp[i][0]),变量dp1代替dp数组中每天持有股票的情况dp[i][1])。

优化后的代码如下:

class Solution {public int maxProfit(int[] prices) {//int [][]dp=new int[prices.length][2];int dp0=0;int dp1=-prices[0];for(int i=1;i<prices.length;i++){dp0=Math.max(dp0,dp1+prices[i]);dp1=Math.max(dp1,-prices[i]);}return dp0;}
}

总结

使用自动机的思想,找到股票买卖问题中的三种操作:买入、卖出、休息,两个状态:持有股票、不持有股票,然后画出状态转换图,根据转换图去写出状态转移方程。使用合适的数据结构去记录状态,本题中只有两个变化的状态,天数和持有情况,因此使用二维数组dp记录每天的持有情况就可以了。要额外考虑一下初态的情况,剩下的就交给循环,递推求解即可。明确最后的结果产生在最后一天的不持有股票状态。

后面还会有增加一个状态的变式:即不再限制只能买卖一次,可以买多次、任意次等,我们增加一个状态即可;还会限制买卖间隔时间,我们修改状态转换图即可。以下列出的题目我都会在本专题第二篇—刷题部分去讲解,到时更新了我会把链接放在这里。

Letcode中股票买卖问题(点击链接跳转去看题):

121.买卖股票的最佳时机(简单):即本文例题,最多只买卖一次。

122.买卖股票的最佳时机II(简单):可以多次买卖,不做任何限定。

123.买卖股票的最佳时机III(困难):最多只能买卖两次。

188.买卖股票的最佳时机IV(困难):最多可以买卖k次。k是变化的。

309.最佳买卖股票时机含冷冻期(中等):卖出和买入之间必须至少间隔一天。

714.买卖股票的最佳实际含手续费(中等):可以多次买卖,但每次买入股票都要额外支付手续费。

参考

  1. labuladong的算法小抄。链接

更多文章

新年算法小专题1.滑动窗口问题(Java)

新年算法小专题1.滑动窗口刷题(Java)

更多文章仍在更新,欢迎收藏本专栏。感谢您的每一个喜欢~

2021新年算法小专题—2.股票买卖利润(Java)相关推荐

  1. 2021新年算法小专题—2.股票买卖利润刷题(Java)

    本篇是股票买卖问题的更多题解,在上一篇文章中我们已经介绍了这一题型,实际上是一类dp问题,我们用自动机的思想去解决,在上一篇中我们以一道限定只买卖一次股票的题目为例进行了讲解,文章链接.下面我们继续完 ...

  2. 二分法、三分法 --算法竞赛专题解析(1)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 2019.8 网购:京东 当当      作者签名书 如有建议, ...

  3. 四边形不等式优化 --算法竞赛专题解析(10)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 2019.8 网购:京东 当当      作者签名书 如有建议, ...

  4. 树形DP --算法竞赛专题解析(17)

    本系列文章将于2021年整理出版,书名<算法竞赛专题解析>. 前驱教材:<算法竞赛入门到进阶> 清华大学出版社 网购:京东 当当      想要一本作者签名书?点我 如有建议, ...

  5. 关于“labuladong的算法小抄”的学习笔记---第0章核心框架汇总的后半部分技巧(c++版)

    目录 前言 一.回溯算法秒杀所有排列/组合/子集问题 回溯和DFS之间区别---遍历树枝or遍历节点 1.子集(元素无重不可复选) 2.组合(元素无重不可复选) 3.排列(元素无重不可复选) 4.子集 ...

  6. labuladong的算法小抄pdf_真漂亮!这份GitHub上爆火的算法面试笔记,助你圆满大厂梦...

    前言 Github作为程序员们的后花园,一直以来都是程序员最喜欢逛逛.学习的地方,小编也不例外,最近看到一份对标BAT等一线大厂的算法面试笔记,已经标星68+K了,很是惊讶,看了一下,觉得知识点整理得 ...

  7. labuladong 的算法小抄_来自GitHub 68.8k star的硬核算法教程

    很多朋友害怕算法,其实大可不必,算法题无非就那几个套路,一旦掌握,就会觉得算法实在是太朴实无华且枯燥了! 本文选自硬核算法教程<labuladong的算法小抄>,带你学习套路,把握各类算法 ...

  8. .[算法]图论专题之最短路径

    .[算法]图论专题之最短路径 作者:jasonkent27 转载请注明出处:www.cnblogs.com/jasonkent27 1. 前言 1.1 最短路引入 小明和小天现在住在海口(C1),他们 ...

  9. 2021谷歌算法排名因素大全

    采自:埃克森数字营销           2021谷歌算法排名因素大全 你可能知道谷歌在他们的算法中使用超过200个排名因素- 但具体这200个排名因素是什么可能很少有人能讲出来. 好吧,你们来对了, ...

最新文章

  1. php 存储数据的方法,在PHP中存储可轻松编辑的配置数据的最快方法?
  2. 【黑金原创教程】【FPGA那些事儿-驱动篇I 】实验二十七:TFT模块 - 显示
  3. 中常用的数据结构_C语言实现常用数据结构:基本概念(第0篇
  4. ios一个app调起另一个app_电商app开发价格:制作一个电商app需要多少钱?
  5. 关于深度学习中GPU显存使用的介绍
  6. 处理数字_9_计算标准差
  7. 【渝粤题库】广东开放大学 微信平台开发与应用 形成性考核 (2)
  8. 图像影音型计算机主板选择什么,电脑主板型号在哪里看? 每日一答
  9. unity asset store下载不了_Unity手游实战:从0开始SLG——资源管理系统-基础篇(三)AssetBundle原理...
  10. Hive高阶分析函数
  11. 互联网反欺诈体系中的常用技术和数据类型
  12. tomcat 使用 memcached管理session ,并且实现统一登录
  13. android 过滤ip,EditText Android过滤器的IP地址###。###。###。###?
  14. 运筹优化学习19:Cplex中文教程与实例详解
  15. keil uVision 注册过期导致编译不通过
  16. 哈理工OJ 2274 Heroic Action(01坑背包)
  17. mysql数据狛聘_.net 生成拼音码与五笔码实例代码
  18. java里arcsin_java编程用泰勒级数计算arcsin
  19. html5 mp4转换ogv格式,FFmpeg 批量转换HTML5视频(mp4/webm/ogv)
  20. 按键精灵脚本:采集鼠标当前所在坐标的颜色

热门文章

  1. 使用迅雷代替SDK Manager加速下载Android SDK
  2. 每日作业 - JavaSE第2天
  3. C++语法学习笔记二十七: 引用折叠,转发、完美转发,forward
  4. 使用R做方差分析实现多重比较可视化结果
  5. 凭借徽商精神,他创办的公司成为国家级电子商务示范企业
  6. 解决办法:error: failed to push some refs to 'https://github.com/xxxx.git'
  7. 数学建模代码速成~赛前一个月~matlab~代码模板~吐血总结~三大模型代码(预测模型、优化模型、评价模型)
  8. TCP三次握手详解:传输控制块TCB以及积极和消极的连接建立方式
  9. 【LIO-SAM论文全文翻译】:LIO-SAM: Tightly-coupled Lidar Inertial Odometry via Smoothing and Mapping
  10. linux显示桌面命令,linux显示桌面快捷键设置