最近在看算法时看到股票类的题目觉得很有意思,在这里对于比较好的方法做一下总结,尽量让大家看一遍就可以记住这个问题该怎么解决。其实学习算法是一个痛苦的过程,但不管怎样最终提升的是自己的能力,所以希望读到文章的小伙伴如果也是同样对算法感兴趣或者没有兴趣但想学习算法的同学,努力坚持,在某一天你就会突然发现,我怎么这么牛哈哈!
不扯闲话了。最近看到有股票相关的算法题目,于是把相关类型的题目做了总结记录在这里,大家在看到一个问题的同时也可以看一下其他的类型的题目。大家可以先不要看后面的解析自己做一下,我在这里的解法也只是给大家提供一个解题的思路和方法,如果大家有更好的解体思路和方式,可以留言,我们大家一起探讨。
对于股票类型的题目,都是求如何获取最大的利益,大致整理如下:

1.买卖股票的最佳时机-最多只允许完成一笔交易(即买入和卖出一支股票一次)
2.买卖股票的最佳时机-最多可以完成 两笔 交易
3.买卖股票的最佳时机-最多可以完成 k 笔交易
4.买卖股票的最佳时机-尽可能地完成更多的交易(多次买卖一支股票)
5.买卖股票的最佳时机-尽可能地完成更多的交易(多次买卖一支股票),卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)
6.买卖股票的最佳时机-可以无限次地完成交易,但是你每笔交易都需要付手续费

可能有的同学对于股票买卖不是很清楚,我在这里大致说一下,一只股票在每一天的价格可能都不一样,你需要选择低价的时候买入,高价的时候卖出才能尽可能获得较高的利益,题目中还有一个要求就是你在买入以后只有卖出之后才能继续进行买卖。例如某个股票五天的价格分别为7,1,5,3,6,4,在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5,同时,你不能在买入前卖出股票。其他不懂得地方大家可以自行百度。接下来我们由简单到复杂来分析一下。
这里我们给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格。

买卖股票的最佳时机-最多只允许完成一笔交易(即买入和卖出一支股票一次)

我们先来看最多只允许完成一笔交易的情况。我们来假设自己来购买股票。随着时间的推移,每天我们都可以选择出售股票与否。那么,假设在第 i 天,如果我们要在今天卖股票,那么我们能赚多少钱呢?显然,如果我们真的在买卖股票,我们肯定会想:如果我是在历史最低点买的股票就好了。这里采用动态规划的思想进行解决。通常采用动态规划解决问题主要步骤分为三步:

1.定义状态
2.找出状态转移方程
3.初始状态确定

那么这里的这三个步骤分别应该怎么定义呢?我们假设动态规划表为dp[i],表示以 prices[i]为结尾的子数组的最大利润,这就定义好了状态;假如计划在第 i 天卖出股票,那么第i日最大利润dp[i] 应该等于前 i - 1 日最大利润 dp[i-1] 和第 i日卖出的最大利润中的最大值,用数学公式表示即:

dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))

以上就是状态转移的方程;
接下来需要定义初始状态dp[0],即首日的收益,显然dp[0]=0,依据以上公式遍历,最后得到的dp[n-1]即为最后的最大利益,其中n为dp列表的长度。
下面是代码:

// 最多只允许完成一笔交易
/*** @Author likangmin* @create 2020/10/27 19:16*/
public int maxProfit(int[] prices) {if(prices==null||prices.length==0){return 0;}int[] dp=new int[prices.length];dp[0]=0;int minPrice=Integer.MAX_VALUE;for(int i=1;i<prices.length;i++){minProce=Math.min(minPrice,prices[i]);dp[i]=Math.max(dp[i-1],prices[i]-minPrice);}return dp[prices.length-1];}

由于 dp[i] 只与 dp[i - 1] , prices[i] , minPrice相关,因此可使用一个变量profit代替 dp列表。优化后的转移方程为:

profit=max(profit,prices[i]−min(minPrice,prices[i])

则代码可以优化为:

// 最多只允许完成一笔交易
/*** @Author likangmin* @create 2020/10/27 19:27*/public static int maxProfit(int[] prices) {int minprice=Integer.MAX_VALUE,profit=0;for(int price:prices){minprice=Math.min(minprice,price);profit=Math.max(profit,price-minprice);}return profit;}

看起来更加简洁,这就解决了最多只允许完成一笔交易的情况。大家对于这类问题有没有了初步的一个解题思路了呢?相信对这个有了一定的了解之后对于后面的几类问题会相对比较容易理解一些。

买卖股票的最佳时机-尽可能地完成更多的交易(多次买卖一支股票)

接下来看一个比较简单的问题:尽可能地完成更多的交易(多次买卖一支股票)。为什么说简单呢?其实只要把题目的意思翻译成大家都通俗易懂的意思,那就会恍然大悟。大家看完以下的解释就会知道,同样你也会觉得非常简单。对于多次买卖没有限定次数的情况,在这里给大家分为三个场景:

单独交易日:假设今天的价格是p1,明天的价格是p2,则今天买入明天卖出可以賺取的利益为p2-p1(负值代表亏损)
连续上涨交易日:设此上涨交易日股票价格分别为p1,p2......pn,则第一天买最后一天卖最大为pn-p1,很多同学可能就是在这里不知道则呢忙处理,其实这个可以等价成每天都买卖,即pn-p1=(p2-p1)+(p3-p2)+......+(pn-pn-1)。
连续下降交易日:不进行买卖的话收益最大。

则依据以上分析,我们可以按照下main的操作进行获取不限次数交易的最大利益:遍历整个股票交易日价格列表,所有上涨交易日都买卖(赚到所有利润),所有下降交易日都不买卖(永不亏钱),具体定义流程如下:

1.设 res为第 i-1 日买入与第 i 日卖出赚取的利润,即 res= prices[i] - prices[i - 1] ;
2.当该天利润为正 res> 0,则将利润加入总利润 profit;当利润为 0 或为负,则直接跳过;
3.遍历完成后,返回总利润 profit即为最大的利润

以下是代码:

// 多次买卖
/*** @Author likangmin* @create 2020/10/27 20:00*/
public int maxProfit(int[] prices) {int profit=0;for(int i=1;i<prices.length;i++){int res=prices[i]-prices[i-1];if(res>0){profit+=res;}}return profit;}

是不是很简单?其实只要你理解了题目的真正意思,那基本都可以按照思路自己写出来。

买卖股票的最佳时机-最多可以完成 两笔 交易

接下里让我们看最多交易两次的问题。
在一定的天数内,我们最多可以进行两次交易,分为 买-卖-买-卖,所以问题的关键就是找到这4个点的最优分布。而第一次买入 / 第一次卖出 / 第二次买入 / 第二次卖出 这四个过程都可能发生在今天。为了更好的说明,在这里我们举一个例子:

我现在要进行股票交易
第一次买入花了2元, 利润为 -2 元
第一次卖出,买了4元,利润为 4-2 = 2 元
第二次买入,花了3元,利润为 2-3 = -1 元
第二次卖出,卖了5元,利润为 -1 + 5 = 4 元

基于以上的一个状态,定义了4个变量与上述4个过程对应:

第一次买入后的状态: minPrice1: 第一次买入后的利润
第一次卖出后的状态: maxProfit1: 第一次卖出后的利润
第二次买入后的状态: maxProfitAtferBuy第二次买入后的利润
第二次卖出后的状态: maxProfit2:第二次卖出后的利润

然后遍历每一天,在每一天我们都作 4个假设,并更新上面4个状态:

假设今天第一次买入:更新 最低价格
假设今天第一次卖出:更新 第一次最大利润
假设今天第二次买入:更新 第二次买后的最大剩余利润
假设今天第二次卖出:更新 当天能获得的最大利润

有了以上的一个分析,那就很容易的将代码写出来了:

// 最多交易两次
/*** @Author likangmin* @create 2020/10/27 20:10*/
public static int maxProfit2(int[] prices) {int minPrice1 = Integer.MAX_VALUE;int maxProfit1 = 0;int maxProfitAfterBuy = Integer.MIN_VALUE;int maxProfit2 = 0;for(int price : prices) {// 1.第一次最小购买价格minPrice1  = Math.min(minPrice1,  price);// 2.第一次卖出的最大利润maxProfit1 = Math.max(maxProfit1, price - minPrice1);// 3.第二次购买后的剩余净利润maxProfitAfterBuy  = Math.max(maxProfitAfterBuy,  maxProfit1 - price );// 4.第二次卖出后,总共获得的最大利润(第3步的净利润 + 第4步卖出的股票钱)maxProfit2 = Math.max(maxProfit2, price + maxProfitAfterBuy);}return maxProfit2;}

买卖股票的最佳时机-最多可以完成 k 笔交易

接下来看交易K次的问题,这个题和两次交易 基本就可以归为一个类型。我们在没进行交易的时候,利润是为 0 的;每次买入,手上的钱会减少,即:利润减少 --;每次卖出,手上的前会增多,即:利润增加 ++。每次的买入状态跟上次的卖出状态相关,每次的卖出状态跟上次的买入状态相关。有的同学可能不太理解,这里做一下简单的例子介绍:

第 1 次买入的时候,上次的状态: 利润为 0 的时候
第 1 次卖出的时候,上次的状态:第 1 次买入
第 3 次买入的时候,上次的状态:第 2 次卖出
...

当然状态都是指操作完成后的利润。这样的我们就很自然地会想到动态规划表来记录这样一个状态变化的过程;我们在每一天都把这 k 次交易都模拟一遍,记录这一天的 k 次交易的状态,并在后续不停的更新。所以先要定义好各个步骤需要的信息:

状态定义:dp[i][j]表示第 i次交易, 行为是「买 | 卖」的状态,其中 j=0 表示买;j = 1 表示卖
状态转移:
第 i 次交易的买入: dp[i][0] = dp[i - 1][1] - price
第 i 次交易的卖出: dp[i][1] = dp[i][0] + price
其中 priceprice 表示当天的价格
初始化:dp[i][0]=−∞ (方便比较)

我们需要注意的是,一次交易比较特殊,单独计算一下 dp[0][0] 和dp[0][1],后面 k-1k−1 次都是基于第一次交易,另外交易数 k>length/2 的时候,k 就没有意义了,因为天数限制了交易次数必须小于 length/2,所以这种情况也就相当于无限次交易。有了上面的分析,接下来我们就很自然的写出代码:

// 交易K次
/*** @Author likangmin* @create 2020/10/27 20:20*/
public int maxProfit(int k, int[] prices) {if (k < 1 ) { return 0; }// k 超过了上限,也就变成了 无限次交易问题if (k > prices.length / 2) {return 不限交易次数(prices);}// 状态定义int [][] dp = new int[k][2];// 初始化for (int i = 0; i < k; i++) {dp[i][0] = Integer.MIN_VALUE;}// 遍历每一天,模拟 k 次交易,计算并更新状态for (int p : prices) {dp[0][0] = Math.max(dp[0][0], 0 - p);                  // 第 1 次买dp[0][1] = Math.max(dp[0][1], dp[0][0] + p);           // 第 1 次卖for (int i = 1; i < k; i++) {dp[i][0] = Math.max(dp[i][0], dp[i - 1][1] - p);   // 第 i 次买dp[i][1] = Math.max(dp[i][1], dp[i][0] + p);       // 第 i 次卖}}return dp[k - 1][1];}

买卖股票的最佳时机-可以无限次地完成交易,但是你每笔交易都需要付手续费

接下来分析不限交易次数但是却会收取手续费的问题。当我们不持有股票时假设最大利润为profit1,当我们持有股票时的最大利润profit2,则在第 i 天时,我们需要根据第 i - 1天的状态来更新 profit1 和 profit2 的值。对于profit1,我们可以保持不变,或者将手上的股票卖出,状态转移方程为:

profit1 = max(profit1 , profit2 + prices[i] - fee)

对于 profit2,我们可以保持不变,或者买入这一天的股票,状态转移方程为:

profit2 = max(profit2 , cash - prices[i])
// 不限次数但收取手续费
/*** @Author likangmin* @create 2020/10/27 20:30*/
public int maxProfit(int[] prices, int fee) {int profit1  = 0, profit2  = -prices[0];for (int i = 1; i < prices.length; i++) {profit1 = Math.max(profit1, profit2 + prices[i] - fee);profit2 = Math.max(profit2, profit1 - prices[i]);}return profit1;}

买卖股票的最佳时机-尽可能地完成更多的交易(多次买卖一支股票),卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)

接下来看最后一个问题:不限交易次数但是有冷冻期。我们用 f[i] 表示第 i 天结束之后的「累计最大收益」。根据题目描述,由于我们最多只能同时买入(持有)一支股票,并且卖出股票后有冷冻期的限制,因此我们会有三种不同的状态:

我们目前持有一支股票,对应的「累计最大收益」记为f[i][0];
我们目前不持有任何股票,并且处于冷冻期中,对应的「累计最大收益」记为f[i][1];
我们目前不持有任何股票,并且不处于冷冻期中,对应的「累计最大收益」记为 f[i][2]。

如何进行状态转移呢?在第 i 天时,我们可以在不违反规则的前提下进行「买入」或者「卖出」操作,此时第 ii 天的状态会从第 i-1 天的状态转移而来;我们也可以不进行任何操作,此时第 i 天的状态就等同于第 i-1 天的状态。那么我们分别对这三种状态进行分析:

对于f[i][0],我们目前持有的这一支股票可以是在第i−1 天就已经持有的,对应的状态为 f[i−1][0];
或者是第 i 天买入的,那么第i−1 天就不能持有股票并且不处于冷冻期中,对应的状态为f[i−1][2]
加上买入股票的负收益prices[i]。因此状态转移方程为:
f[i][0]=max(f[i−1][0],f[i−1][2]−prices[i])
对于f[i][1],我们在第 i 天结束之后处于冷冻期的原因是在当天卖出了股票,那么说明在第 i−1 天时我们
必须持有一支股票,对应的状态为f[i−1][0] 加上卖出股票的正收益prices[i]。因此状态转移方程为:
f[i][1]=f[i−1][0]+prices[i]
对于 f[i][2],我们在第 i 天结束之后不持有任何股票并且不处于冷冻期,说明当天没有进行任何操作,
即第 i−1 天时不持有任何股票:如果处于冷冻期,对应的状态为 f[i−1][1];如果不处于冷冻期,对应的状态为 f[i−1][2]。
因此状态转移方程为:
f[i][2]=max(f[i−1][1],f[i−1][2])

这样我们就得到了所有的状态转移方程。如果一共有 n 天,那么最终的答案即为:

max(f[n−1][0],f[n−1][1],f[n−1][2])

注意到如果在最后一天(第 n-1n−1 天)结束之后,手上仍然持有股票,那么显然是没有任何意义的。因此更加精确地,最终的答案实际上是f[n−1][1] 和 f[n−1][2] 中的较大值,即:

max(f[n−1][1],f[n−1][2])

初始值应该怎么定义呢?在第 0 天时,如果持有股票,那么只能是在第 0 天买入的,对应负收益 −prices[0];如果不持有股票,那么收益为零,即:

f[0][0]=−prices[0]
f[0][1]=0
f[0][2]=0

有了以上条件则可以直接写出代码:

// 不限次数但有冷冻期
/*** @Author likangmin* @create 2020/10/27 20:50*/
public int maxProfit4(int[] prices) {if (prices.length == 0) {return 0;}int n = prices.length;// f[i][0]: 手上持有股票的最大收益// f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益// f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益int[][] f = new int[n][3];f[0][0] = -prices[0];for (int i = 1; i < n; ++i) {f[i][0] = Math.max(f[i - 1][0], f[i - 1][2] - prices[i]);f[i][1] = f[i - 1][0] + prices[i];f[i][2] = Math.max(f[i - 1][1], f[i - 1][2]);}return Math.max(f[n - 1][1], f[n - 1][2]);}

关于股票相关类型已分析完了。同学们有没有豁然开朗的感觉?其实大部分解题的思想都是基于动态规划,对动态规划还不是很熟的同学可以自己找资料找资料学习一下。只要搞懂了其中的原理,写出状态转移方程,那么问题基本上就可以迎刃而解了。我这里只是提供一个解体的大概思路,并不代表是最好的解法,如果有更好解法且更让人一看就懂的大家可以提出来一起学习。
注:如转载,请注明原始地址。

算法分析股票类型的相关题型相关推荐

  1. python映射类型-python映射类型的相关介绍

    映射类型是一类可迭代的键-值数据项的组合,提供了存取数据项及其键和值的方法,在python3中,支持两种无序的映射类型:内置的dict和标准库中的collections.defaultdict类型. ...

  2. 采购订单增强(根据公司代码、采购订单类型等相关订单信息给采购订单税码设置默认值)

    根据公司代码.采购订单类型等相关订单信息给采购订单税码设置默认值: 使用BADI及方法: BADI : ME_PROCESS_PO_CUST Method: PROCESS_ITEM 在Method ...

  3. 无法嵌入互操作类型 请改用适用的接口_机器视觉可用的不同电缆和连接器类型以及相关利弊分析...

    为您的机器视觉应用选择正确的接口是您选择摄像头过程的一项决定因素.以下小节内容将概述机器视觉可用的不同电缆和连接器类型以及相关利弊. 机器视觉接口一般有两种形式:专用型和消费型. 专用型接口 适用于需 ...

  4. 磁盘类型和相关术语学习笔记

    磁盘类型和相关术语 在 Linux 中一切皆文件,但是类型不同.例如使用 ls -l 对于设备文件和普通文件有一部分内容是不同的,即普通文件有大小,而设备文件有主设备号和次设备号,没有大小. # ll ...

  5. Go : 与切片类型处理相关的代码(附完整源码)

    Go : 与切片类型处理相关的代码 package codegenfunc SliceClear(s []int) []int {// amd64:`.*memclrNoHeapPointers`fo ...

  6. 股票应用开发——相关指标的含义(二)

            上一篇博客主要是网络数据的获取以及解析,本篇博客将要讲述的是股票开发过程中相关指标的计算以及含义!在这里声明一点就是,不在贴出指标的计算代码,出于相关考虑敬请谅解!如有需要可以留言咱们 ...

  7. python 映射和反映射_python映射类型的相关介绍

    映射类型是一类可迭代的键-值数据项的组合,提供了存取数据项及其键和值的方法,在python3中,支持两种无序的映射类型:内置的dict和标准库中的collections.defaultdict类型. ...

  8. C++实现:自定义数组类型实现相关运算符重载

    运算符重载说明 这次我们通过来自定义数组类型重载[].<<.>>.==.!=运算符来学习C++运算符重载相关知识. C++的运算符重载是C++相对于其他编程语言的一大特色. 在 ...

  9. mysql 视图锁_Oracle数据库的锁类型及相关视图

    Oracle数据库的锁类型 根据保护的对象不同,Oracle数据库锁可以分为以下几大类:DML锁(data locks,数据锁),用于保护数据的完整性:DDL锁(dictionary locks,字典 ...

最新文章

  1. 认识httphandler
  2. clustalw序列比对_序列比对(二)
  3. [P1580] yyy loves Easter_Egg I
  4. android中绘图Paint.setAntiAlias()和Paint.setDither()方法的作用
  5. RecyclerView的各种版本兼容问题处理集锦
  6. 【毕业设计】PHP公共课平时成绩查询系统(源代码+论文+答辩PPT)
  7. delphi接口带上请求头是什么意思_python接口自动化(二十)--token登录(详解)...
  8. 基于SSM的理财系统
  9. 【纸牌识别】基于matlab灰度二值化纸牌识别【含Matlab源码 464期】
  10. 桌面云之深信服VMP管理
  11. 简述前端MVVM框架
  12. 图数据库扫盲和图数据选用
  13. pdfpcell输出换行_fpdf 的cell 中文自动换行问题
  14. 【计组5.5】指令流水线
  15. koa2 mysql sequelize_[转]使用nodejs-koa2-mysql-sequelize-jwt 实现项目api接口
  16. 在美国godaddy虚拟主机上用php发邮件应该注意的问题
  17. unity3d绘画手册-------地形高度调节
  18. python都学什么啊-那些效率高的人都在偷偷学什么?
  19. ElasticSearch实战(七)-BKD-Tree 多维空间树算法(范围查询算法)
  20. C8051F340串口通信程序(UART0)

热门文章

  1. VC6程序申请管理员权限
  2. 带你揭秘Web前端发展的前景以及技术
  3. MySQL存储引擎精讲(附带各种存储引擎的对比)
  4. 2、Java Swing JFrame和JPanel:窗口容器和面板容器
  5. 1017 Queueing at Bank (25 分) 【未完成】【难度: 中 / 知识点: 模拟】
  6. 数据库视图的概念和相关操作合集
  7. Java的Map遍历
  8. android布局如何空行,借用你的思路和框架,修复了空行、偶尔setText无效、padding设置的bug...
  9. Java 将 Word 文档转换为 PDF 的完美工具
  10. java基础(三) 加强型for循环与Iterator