2018-04-19 19:28:21

股票问题是leetcode里一条非常经典的题目,因为其具有一定的现实意义,所以还是在数学建模方面还是有很多用武之地的。这里会对stock的给出一个比较通用的解法,然后会针对各个细分问题用通解去解决,主要采用的算法是动态规划算法。

问题描述:Given an array representing the price of stock on each day, what determines the maximum profit we can obtain?

问题求解:首先很容易想到的是最大收入和具体结算时间以及这些天里的最多交易笔数有关,因此我们可以定义一个数组T[i][k],表示在第i天结束时,最多经过k笔交易所产生的最大收入。很显然的,我们可以得到一个初始条件T[-1][k] = T[i][0] = 0,也就是说在第-1天,无论经过多少笔交易都没有收益,在第i天,如果最多为0次交易,那么也将不会产生收益。根据动态规划的一般思路,我们现在需要做的就是建立T[i][k]和T[i-1][k], T[i][k-1], T[i-1][k-1], ...之间的关系下面将介绍如何建立这种关系。

对于第i天的交易,我们可以采取的策略有sell,buy,rest。我们当然在最初的时候并不知道哪个选择是最优的,所以我们需要去判断一下哪个选择对于我们来说能得到一个好的结果。由于有每次只能手持一股的限制,所以我们无法在已经买过股票的情况下再购买股票,同时也无法在已经卖空股票的情况下再继续卖股票,因此,我们需要一个变量来揭示第i天结束时,手里剩余的股票数量。

因此T[i][k]的定义需要被拆分成两个:T[i][k][0] and T[i][k][1]。并且我们还可以得到如下的初始条件和递推关系:

  1. Base cases:
    T[-1][k][0] = 0, T[-1][k][1] = -Infinity
    T[i][0][0] = 0, T[i][0][1] = -Infinity

  2. Recurrence relations:
    T[i][k][0] = max(T[i-1][k][0], T[i-1][k][1] + prices[i])
    T[i][k][1] = max(T[i-1][k][1], T[i-1][k-1][0] - prices[i])

注意:定义一次交易为买入的时候就算作一笔交易,不可等到卖出才算作交易。

  • 121. Best Time to Buy and Sell Stock

问题描述:

问题求解:

k = 1,则递推式变为:

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

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

对空间复杂度进行优化,可以在<O(n),O(1)>完成求解。

    public int maxProfit(int[] prices) {
int Ti10 = 0;
int Ti11 = Integer.MIN_VALUE;
for (int price : prices) {
Ti10 = Math.max(Ti10, Ti11 + price);
Ti11 = Math.max(Ti11, -price);
}
return Math.max(Ti10, Ti11);
}
  • 122. Best Time to Buy and Sell Stock II

问题描述:

问题求解:

k = INF,则递推式变为:

T[i][k][0] = max(T[i - 1][k][0],T[i - 1][k][1] + prices[i])

T[i][k][1] = max(T[i - 1][k][1],T[i - 1][k][0] - prices[i])

对空间复杂度进行优化,可以在<O(n),O(1)>完成求解。

    public int maxProfit(int[] prices) {
int Tik0 = 0;
int Tik1 = Integer.MIN_VALUE;
for (int price : prices) {
int tmp = Tik0;
Tik0 = Math.max(Tik0, Tik1 + price);
Tik1 = Math.max(Tik1, tmp - price);
}
return Math.max(Tik0, Tik1);
}
  • 714. Best Time to Buy and Sell Stock with Transaction Fee

问题描述:

问题求解:

k = INF with fee,则递推式变为:

T[i][k][0] = max(T[i - 1][k][0],T[i - 1][k][1] + prices[i])

T[i][k][1] = max(T[i - 1][k][1],T[i - 1][k][0] - prices[i] - fee)

对空间复杂度进行优化,可以在<O(n),O(1)>完成求解。

    public int maxProfit(int[] prices, int fee) {
int Tik0 = 0;
int Tik1 = Integer.MIN_VALUE;
for (int i = 0; i < prices.length; i++) {
int tmp = Tik0;
Tik0 = Math.max(Tik0, Tik1 + prices[i]);
Tik1 = Math.max(Tik1, tmp - prices[i] - fee);
}
return Tik0;
}
  • 309. Best Time to Buy and Sell Stock with Cooldown

问题描述:

问题求解:

k = INF with cooldown,则递推式变为:

T[i][k][0] = max(T[i - 1][k][0],T[i - 1][k][1] + prices[i])

T[i][k][1] = max(T[i - 1][k][1],T[i - 2][k][0] - prices[i] )

对空间复杂度进行优化,可以在<O(n),O(1)>完成求解。

    public int maxProfit(int[] prices) {
int Tik0 = 0;
int Tik1 = Integer.MIN_VALUE;
int prev = 0;
for (int price : prices) {
int tmp = Tik0;
Tik0 = Math.max(Tik0, Tik1 + price);
Tik1 = Math.max(Tik1, prev - price); // T[i - 1][k][0] 若是 rest,则T[i - 1][k][0] = T[i - 2][k][0],若卖出,则对于第 i 天 buy 只能选择T[i - 2][k][0]
prev = tmp;
}
return Math.max(Tik0, Tik1);
}
  • 123. Best Time to Buy and Sell Stock III

问题描述:

问题求解:

k = 2,则递推式变为:

T[i][2][0] = max(T[i - 1][2][0],T[i - 1][2][1] + prices[i])

T[i][2][1] = max(T[i - 1][2][1],T[i - 1][1][0] - prices[i])

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

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

对空间复杂度进行优化,可以在<O(n),O(1)>完成求解。

    public int maxProfit(int[] prices) {
int Ti20 = 0;
int Ti21 = Integer.MIN_VALUE;
int Ti10 = 0;
int Ti11 = Integer.MIN_VALUE;
for (int price : prices) {
Ti20 = Math.max(Ti20, Ti21 + price);
Ti21 = Math.max(Ti21, Ti10 - price);
Ti10 = Math.max(Ti10, Ti11 + price);
Ti11 = Math.max(Ti11, -price);
}
return Math.max(Ti20, Ti21);
}
  • 188. Best Time to Buy and Sell Stock IV

问题描述:

问题求解:

回到最初讨论的问题,k是一个有限常数,则递推式为:

T[i][k][0] = max(T[i - 1][k][0],T[i - 1][k][1] + prices[i])

T[i][k][1] = max(T[i - 1][k][1],T[i - 1][k - 1][0] - prices[i])

需要注意到,当k > n / 2的时候,k就可以看作是INF,因为同一天卖出买入等效于这一天没有操作,所以最多的k次有效交易为n / 2,超过了这个数就有废操作了。

另外,此时每一天有k个状态,所以可以用两个k + 1大小的数组来保存状态量,最终在<O(kn),O(k)>完成求解。

    public int maxProfit(int k, int[] prices) {
if (k > prices.length >>> 1) {
int Tik0 = 0;
int Tik1 = Integer.MIN_VALUE;
for (int price : prices) {
int tmp = Tik0;
Tik0 = Math.max(Tik0, Tik1 + price);
Tik1 = Math.max(Tik1, tmp - price);
}
return Math.max(Tik0, Tik1);
} int[] Tik0 = new int[k + 1];
int[] Tik1 = new int[k + 1];
Arrays.fill(Tik1, Integer.MIN_VALUE);
for (int price : prices) {
for (int i = k; i > 0; i--) {
Tik0[i] = Math.max(Tik0[i], Tik1[i] + price);
Tik1[i] = Math.max(Tik1[i], Tik0[i - 1] - price);
}
}
return Math.max(Tik0[k], Tik1[k]);
}

本题如果使用原始的递推式来解的话可以这么修改,因为-1在数组中是没有办法进行初始化的,于是我们可以这样定义递推公式:

dp[i][k][0] : 经过了i天并使用了k次交易后剩余0股的最大利润;

dp[i][k][1] : 经过了i天并使用了k次交易后剩余1股的最大利润;

于是dp[0][k][0] = 0;dp[0][k][1] = Integer.MIN_VALUE,这样就可以避免上述的问题。

    public int maxProfit(int k, int[] prices) {
int n = prices.length;
if (k >= n / 2) return helper(prices);
int[][][] dp = new int[k + 1][n + 1][2];
for (int i = 0; i <= n; i++) {
dp[0][i][0] = 0;
dp[0][i][1] = Integer.MIN_VALUE;
}
for (int i = 0; i <= k; i++) {
dp[i][0][0] = 0;
dp[i][0][1] = Integer.MIN_VALUE;
}
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j][0] = Math.max(dp[i][j - 1][0], dp[i][j - 1][1] + prices[j - 1]);
dp[i][j][1] = Math.max(dp[i][j - 1][1], dp[i - 1][j - 1][0] - prices[j - 1]);
}
}
return dp[k][n][0];
} private int helper(int[] prices) {
int tik0 = 0;
int tik1 = Integer.MIN_VALUE;
for (int price : prices) {
int tmp = tik0;
tik0 = Math.max(tik0, tik1 + price);
tik1 = Math.max(tik1, tmp - price);
}
return tik0;
}

【注意】

这次在解决这个问题的时候出现了MLE的情况,原本以为是三维的数组超了内存,但是实际是没有对k的大小进行判断。

所以这里对k的大小进行提前判断是必要的,如果不判断,直接使用dp那么就会MLE。

这里还有一个问题需要考虑,就是一般来说,我们会将问题规约到可以解决的更小的问题上,而且一般是对k进行外层遍历。

换言之,就是和问题给的方式相同,我们先算k次交易各个天数的最大结果,之后下一轮计算k + 1次的,这里没有这么计算的原因很简单,就是在递推式中第i天的交易总额和第i - 1天的k + 1次交易也有关联,也就是在子问题中k的规模并没有更小,所以必须针对每一天算出所有的k后在计算下一天。

    public int maxProfit(int k, int[] prices) {
int days = prices.length;
if (k >= days / 2) return helper(prices);
int[][] dp = new int[k + 1][2];
for (int i = 0; i <= k; i++) {
dp[i][0] = 0;
dp[i][1] = Integer.MIN_VALUE;
}
for (int price : prices) {
for (int i = 1; i <= k; i++) {
dp[i][0] = Math.max(dp[i][0], dp[i][1] + price);
dp[i][1] = Math.max(dp[i][1], dp[i - 1][0] - price);
}
}
return Math.max(dp[k][0], dp[k][1]);
} private int helper(int[] prices) {
int Tik0 = 0;
int Tik1 = Integer.MIN_VALUE;
for (int price : prices) {
Tik0 = Math.max(Tik0, Tik1 + price);
Tik1 = Math.max(Tik1, Tik0 - price);
}
return Math.max(Tik0, Tik1);
}

  

动态规划-Stock Problem的更多相关文章

  1. 二维剪板机下料问题(2-D Guillotine Cutting Stock Problem) 的混合整数规划精确求解——数学规划的计算智能特征

    二维剪板机下料问题(2-D Guillotine Cutting Stock Problem) 的混合整数规划精确求解——数学规划的计算智能特征 二维剪板机下料(2D-GCSP) 的混合整数规划是最优 ...

  2. LeetCode 309. Best Time to Buy and Sell Stock with Cooldown (stock problem)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  3. LeetCode 188. Best Time to Buy and Sell Stock IV (stock problem)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  4. LeetCode 123. Best Time to Buy and Sell Stock III (stock problem)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  5. 精帖转载(关于stock problem)

    Note: this is a repost(重新投寄) of my original post here with updated solutions(解决方案) for this problem ...

  6. LeetCode 122. Best Time to Buy and Sell Stock II (stock problem)

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...

  7. LeetCode 121. Best Time to Buy and Sell Stock (stock problem)

    Say you have an array for which the ith element is the price of a given stock on day i. If you were ...

  8. Codeforces Round #598 (Div. 3)- E. Yet Another Division Into Teams - 动态规划

    Codeforces Round #598 (Div. 3)- E. Yet Another Division Into Teams - 动态规划 [Problem Description] 给你\( ...

  9. 2019-ACM-ICPC-南京区网络赛-D. Robots-DAG图上概率动态规划

    2019-ACM-ICPC-南京区网络赛-D. Robots-DAG图上概率动态规划 [Problem Description] ​ 有向无环图中,有个机器人从\(1\)号节点出发,每天等概率的走到下 ...

随机推荐

  1. 170703、springboot编程之模板使用(thymeleaf、freemarker)

    官方不推荐集成jsp,关于使用jsp模板我这里就不赘述,如果有需要的,请自行百度! thymeleaf的使用 1.在pom中增加thymeleaf支持 <dependency> <g ...

  2. apt-get install 和 pip install的区别

    pip install apt-get install 源是pyPI 源是ubuntu仓库 对于同一个包,pyPI可以提供更多的版本以供下载 pip install安装的python包,可以只安装在当 ...

  3. python中的接口和依赖注入

    首先,我们必须明确的一点是:python里无接口类型,定义接口只是一个人为规定,在编程过程自我约束 python的类是可以写任意个方法的 定义一个接口对继承类进行约束,接口里有什么方法,继承类就必须有 ...

  4. Most efficient way to get the last element of a stream

    Do a reduction that simply returns the current value: Stream<T> stream; T last = stream.reduce ...

  5. Linux(CentOS)系统下搭建svn服务器

    由于GitHub的私有项目需要收费,gitlab对服务器的要求必须是4GB内存以上.对于一些个人的小型项目,想要免费的版本控制工具来管理自己的代码,又不想代码公开,无疑SVN是比较好的选择.windo ...

  6. POJ2506:Tiling(递推+大数斐波那契)

    http://poj.org/problem?id=2506 #include <iostream> #include <stdio.h> #include <strin ...

  7. 协变返回类型---《C++必知必会》 条款 31

    一般来说,一个重写的函数与被它重写的函数具有相同的返回类型. 然而,这个规则对于“协变返回类型(covariant  return type)“的情形来说有所放松.也就是说,如果B是一个类类型,并且一 ...

  8. hdu4758 Walk Through Squares

    地址:http://acm.split.hdu.edu.cn/showproblem.php?pid=4758 题目: Walk Through Squares Time Limit: 4000/20 ...

  9. javascript模式(2)--模块模式

    在nodeJs中,可以定义自己的模块,然后通过exports来暴露API.一般是这么写的:模块依赖,私有成员和要暴露的对象.在原生js中也可以有类似的写法来组织自己的代码.可以提供一个松耦合.结构清晰 ...

  10. C#——图片操作类简单封装

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Dr ...