• 2020-10-15

    “知识的诅咒”:一旦我们知道某样东西,我们就会发现很难想象不知道它的时候会是什么样子。

一、概述

leetcode中的股票相关的题目如下:

打开看很容易就知道是求最大收益,由于是求最值,很容易就想到要使用动态规划。
其实动态规划就是使用了穷举,但是因为这类问题存在「重叠⼦问题」,可以使用DP table来优
化穷举过程,记录过计算的结果,避免不必要的计算。

动态规划三要素

  • 重叠⼦问题(如果暴力解决,存在大量运算,可以使用备忘录(DP table)来解决)
  • 最优⼦结构
    要符合「最优⼦结构」(子结构之间追求最优是独立的),⼦问题间必须互相独⽴。
  • 状态转移⽅程
    你把 f(n) 想做⼀个状态 n,这个状态 n 是由状态 n - 1 和状态 n - 2 相加转移⽽来,这就叫状态转移,仅此⽽已。

其实最难的是如何写出动态转移方程,这个转移方程要求能够穷举(中途可能经过判断忽略一些不可能的计算)且不重复。

二、股票相关问题模板

该模板来自大佬labuladong,其网站文章讲的更详细:https://labuladong.gitbook.io/algo/di-ling-zhang-bi-du-xi-lie/tuan-mie-gu-piao-wen-ti。
本人只是用自己的理解复述一下。

状态转移方程的实现:

  • 先利⽤「状态」进⾏穷举,找到能表示股票交易过程中状态的方法。
  • 状态转移方程(利用买卖、持有的关系,建立状态机)。
  • 定义 base case(这些值可以代入初始时状态转移方程验证一下),即最简单的情况。

我们的结果是求最大收益,所以状态方程的结果肯定是收益,由这些题目可以得出有两个条件约束最后的收益。
这两个条件是时间(冻结时间等)、交易次数。

再由于状态转移方程中,第n次的最值,需要由前面的第n-1次(甚至前面的n-2次等)转移得到。前面n-1次的持有或者不持有的状态也会影响(一般在思考状态转移方程如何转换才会想到),所以现在有三个状态:时间、交易次数限制、是否持有。
所以可以说是三维DP问题。

  • 得到模板如下:

    // i表示日期(第几天)、k表示还能交易的次数、第三位表示该天是持有还是不持有股票
    //  dp[i][k][0/1] 表示到第i天持有的最大利润(利用一个三维数组来实现)//状态转移⽅程:
    dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
    //解释:今天我没有持有股票,有两种可能:
    //要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
    //要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
    dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
    //解释:今天我持有着股票,有两种可能:
    //要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
    //要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
    

    状态转移方程需要base case,也就是最开始的时候,需要一些初始值来让转移方程开始动作。(比如这里要得出的第一天的情况,你就需要构造出第0天的情况(虽然不存在,但是你得合理构造才能利用dp0推导出dp1))

    // 最简单情况(-1是由于状态转移方程的需要所以构造)
    base case:
    dp[-1][k][0] = dp[i][0][0] = 0
    dp[-1][k][1] = dp[i][0][1] = -infinity(负无穷)//对base case的解释
    dp[-1][k][0] = 0
    解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0。
    dp[-1][k][1] = -infinity
    解释:还没开始的时候,是不可能持有股票的,⽤负⽆穷表⽰这种不可能。
    dp[i][0][0] = 0
    解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0。
    dp[i][0][1] = -infinity
    解释:不允许交易的情况下,是不可能持有股票的,⽤负⽆穷表⽰这种不可能。
    

三、具体问题,具体分析

  • 121、买卖股票的最佳时机

    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
    如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
    注意:你不能在买入股票前卖出股票。

    这道题限制了只能交易一次,相当于上文中的k不用考虑,所以状态变成了两个,简单很多。

    int maxProfit(int* prices, int pricesSize){if(pricesSize == 0)return 0;// base caseint dp_i_0 = 0;int dp_i_1 = -prices[0];//状态转移方程从第1天开始转移,直到最后一天得到结果for(int i=0; i<pricesSize; ++i){dp_i_0 = (dp_i_0 > (dp_i_1+prices[i]))? dp_i_0 : (dp_i_1+prices[i]);dp_i_1 = (dp_i_1 > (-prices[i])) ? dp_i_1 : (-prices[i]); //因为只能交易一次,dp_i_1只会记录买入的花费}return dp_i_0;
    }
    
  • 122.、买卖股票的最佳时机 II

    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
    设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
    注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

    int maxProfit(int* prices, int pricesSize){if(pricesSize == 0)return 0;// base case:初始化的值为第一天的收益int dp_i_0 = 0;int dp_1_1 = -prices[0];for(int i=0; i<pricesSize; ++i){dp_i_0 = (dp_i_0 > (dp_1_1+prices[i]))? dp_i_0 : (dp_1_1+prices[i]);dp_1_1 = (dp_1_1 > (dp_i_0-prices[i])) ? dp_1_1 : (dp_i_0-prices[i]);}return dp_i_0;
    }
    
  • 309.、最佳买卖股票时机含冷冻期

    给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​
    设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
    你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
    卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

    由于存在冷冻期,所以得多记录前两天未持有股票的情况,状态转移方程也要适当变换。

    int maxProfit(int* prices, int pricesSize){int dp_i_0 = 0;int dp_i_1 = -0x7FFFFFFF;int dp_i_2_0 = 0;for(int i=0; i<pricesSize; ++i){int temp = dp_i_0;dp_i_0 = (dp_i_0 > (dp_i_1+prices[i]))?  dp_i_0 : (dp_i_1+prices[i]);dp_i_1 = (dp_i_1 > (dp_i_2_0-prices[i]))? dp_i_1 : (dp_i_2_0-prices[i]);dp_i_2_0 = temp;}return dp_i_0;
    }
    
  • 714.、买卖股票的最佳时机含手续费

    给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。
    你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
    返回获得利润的最大值。
    注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

    这里只是需要把花费加上而已,和122题相差不大。

    int maxProfit(int* prices, int pricesSize, int fee){int dp_i_0 = 0;int dp_i_1 = -0x7FFFFFFF; //用一个最小的数表示不可能的结果,让状态转移方程避开这种情况for(int i=0; i<pricesSize; ++i){dp_i_0 = (dp_i_0 > (dp_i_1+prices[i]))? dp_i_0 : (dp_i_1+prices[i]);dp_i_1 = (dp_i_1 > (dp_i_0-prices[i]-fee)) ? dp_i_1 : (dp_i_0-prices[i]-fee);}return dp_i_0;
    }
    
  • 123、 买卖股票的最佳时机 III

    给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
    设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
    注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

    既然最多只能交易两次,那就把两次的分开就行了。(这里只用四个变量保存其中的结果,因为一些值已经不会对结果产生影响)。

    int maxProfit(int* prices, int pricesSize){//base caseint dp_i_k_0 = 0; //dp[i][k][0]int dp_i_k_1 = -0x7FFFFFFF; //dp[i][k][1]用一个最小的数表示不可能的结果,让状态转移方程避开这种情况int dp_i_k1_0 = 0;//dp[i][k-1][0]int dp_i_k1_1 = dp_i_k_1;//dp[i][k-1][1]for(int i=0; i<pricesSize; ++i){// k=1dp_i_k1_0 = (dp_i_k1_0 > (dp_i_k1_1+prices[i]))? dp_i_k1_0 : (dp_i_k1_1+prices[i]);dp_i_k1_1 = (dp_i_k1_1 > (-prices[i])) ? dp_i_k1_1 : (-prices[i]); //k=2dp_i_k_0 = (dp_i_k_0 > (dp_i_k_1+prices[i]))? dp_i_k_0 : (dp_i_k_1+prices[i]);dp_i_k_1 = (dp_i_k_1 > (dp_i_k1_0-prices[i])) ? dp_i_k_1 : (dp_i_k1_0-prices[i]);}return dp_i_k_0; //最多交易k次的最大收益
    }
    
  • 188、买卖股票的最佳时机 IV

    给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
    设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
    注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)

    这道题是真难呀,看一下大神的解释:四种解法+图解 188.买卖股票的最佳时机 IV

四、参考资料

  • labuladong的算法小抄
  • 四种解法+图解 188.买卖股票的最佳时机 IV

动态规划系列 之 股票相关问题 (C语言刷leetcode)相关推荐

  1. c语言编程 插队排身高,【C语言刷LeetCode】406. 根据身高重建队列(M)

    [ 假设有打乱顺序的一群人站成一个队列. 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数. 编写一个算法来重建这个队列. 注意: 总人数少于110 ...

  2. 九十三、动态规划系列之股票问题(下)

    @Author:Runsen 动态规划必须要面对股票系列,背包系列差不多了,那就上吧. 股票买卖这一类的问题,都是给一个输入数组,里面的每个元素表示的是每天的股价,并且你只能持有一支股票(也就是你必须 ...

  3. 九十二、动态规划系列之股票问题(上)

    @Author:Runsen 动态规划必须要面对股票系列,背包系列差不多了,那就上吧. 文章目录 买卖股票的最佳时机(买一次) 买卖股票的最佳时机(买 N 次) 买卖股票的最佳时机(买 2 次) 买卖 ...

  4. 【C语言刷LeetCode】883. 三维形体投影面积(E)

    [ 格 grid 中,我们放置了一些与 x,y,z 三轴对齐的 1 x 1 x 1 立方体. 每个值 v = grid[i][j] 表示 v 个正方体叠放在单元格 (i, j) 上. 现在,我们查看这 ...

  5. 【C语言刷LeetCode】717. 1 比特与 2 比特字符(E)

    [ 有两种特殊字符: 第一种字符可以用一比特 0 表示 第二种字符可以用两比特(10 或 11)表示 给你一个以 0 结尾的二进制数组 bits ,如果最后一个字符必须是一个一比特字符,则返回 tru ...

  6. 【C语言刷LeetCode】2126. 摧毁小行星(M)

    [ 给你一个整数 mass ,它表示一颗行星的初始质量.再给你一个整数数组 asteroids ,其中 asteroids[i] 是第 i 颗小行星的质量. 你可以按 任意顺序 重新安排小行星的顺序, ...

  7. 【C语言刷LeetCode】235. 二叉搜索树的最近公共祖先(E)

    [ 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 百度百科中最近公共祖先的定义为:"对于有根树 T 的两个结点 p.q,最近公共祖先表示为一个结点 x,满足 x 是 p.q ...

  8. 【量化策略系列】股票均值回归策略之一——配对交易策略(Pairs Trading)

    本文持续更新中.最后更新时间:11/11/2019 文章目录 1. 往期文章回顾 2. 均值回归策略简介 3. 配对交易策略简介 4. 配对交易策略构建流程 5. 代码实现与回测结果 Python 代 ...

  9. 九十四、动态规划系列之路径问题

    @Author:Runsen 在动态规划最短路径经常提及,在上几篇介绍过相关的最短路径的问题,介绍过使用Dijkstra算法去求解,但是Dijkstra算法是基于贪心算法,按路径长度递增的次序一步一步 ...

最新文章

  1. 《Python数据可视化编程实战》——5.5 用OpenGL制作动画
  2. 某程序员吐槽一程序员大佬竟然放弃百度offer,回老家进烟草公司!是不是脑子有坑?网友:你才脑子有坑!...
  3. GAN的基本原理、应用和走向
  4. LTE 中的RV版本
  5. python编程定义圆_Python语言编程系列014——PyQt中自定义圆形指示灯
  6. 基于ASP.NET 3.5 Web Service 的JSON扩展应用
  7. 【JS】实时监控页面,input框数值自动求和
  8. Nginx记录客户端POST过来的具体信息
  9. 小程序本地图片偶尔加载不出来_小程序优化的20中策略
  10. Atitit.wrmi web rmi框架新特性
  11. EtherCAT工业以太网的主要特点
  12. linux+硬盘rd5,BackTrack5(BT5)硬盘安装完美教程 亲测可用
  13. 苹果计算机怎么显示汉字,苹果的safari浏览器怎样设定成中文显示
  14. java Locale类使用
  15. 8虚拟内存9页面置换算法
  16. 计算机专业开题报告这么写,有效有用还能过
  17. 靠着群友的接济,一毛不拔的学会了Python!(学习路线+资料分享)
  18. 高德地图:弧度飞线图层详解
  19. tar 解压出指定文件
  20. 用这几个扫一扫识别文字的软件,告别办公烦恼

热门文章

  1. 机器学习通俗入门-Softmax 求解多类分类问题
  2. 商品表(spu)、规格表(sku)设计
  3. 在 Apple 芯片设备上用 Android Studio?别忘了使用 Apple 芯片预览版!
  4. 前端角度如何做好SEO
  5. 移动固态硬盘删除分区(包括EFI分区)
  6. CAD制图初学入门教程之立剖网格
  7. [JAVA加解密]RSA算法、ElGamal算法
  8. 从零开始实现一个MQTT客户端 开篇漫谈
  9. 利用树莓派制作人体感应监控器
  10. Cocos Creator苹果应用商城上架指南