动态规划-Stock Problem
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]。并且我们还可以得到如下的初始条件和递推关系:
Base cases:
T[-1][k][0] = 0, T[-1][k][1] = -InfinityT[i][0][0] = 0, T[i][0][1] = -InfinityRecurrence 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的更多相关文章
- 二维剪板机下料问题(2-D Guillotine Cutting Stock Problem) 的混合整数规划精确求解——数学规划的计算智能特征
二维剪板机下料问题(2-D Guillotine Cutting Stock Problem) 的混合整数规划精确求解——数学规划的计算智能特征 二维剪板机下料(2D-GCSP) 的混合整数规划是最优 ...
- 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 ...
- 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 ...
- 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 ...
- 精帖转载(关于stock problem)
Note: this is a repost(重新投寄) of my original post here with updated solutions(解决方案) for this problem ...
- 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 ...
- 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 ...
- Codeforces Round #598 (Div. 3)- E. Yet Another Division Into Teams - 动态规划
Codeforces Round #598 (Div. 3)- E. Yet Another Division Into Teams - 动态规划 [Problem Description] 给你\( ...
- 2019-ACM-ICPC-南京区网络赛-D. Robots-DAG图上概率动态规划
2019-ACM-ICPC-南京区网络赛-D. Robots-DAG图上概率动态规划 [Problem Description] 有向无环图中,有个机器人从\(1\)号节点出发,每天等概率的走到下 ...
随机推荐
- eclipse/myeclipse 中的一些常用的快捷键
一.eclipse 中的一些常用的快捷键 Eclipse的编辑功能非常强大,掌握了Eclipse快捷键功能,能够大大提高开发效率. Eclipse中有如下一些和编辑相关的快捷键. 1. [ALT+/] ...
- 在centos上用nginx访问php显示404
yum install nginx -y 可以在浏览器 访问html文件,但是访问不了php文件 后来我看见别人别人响应的参数有php,而我没有我,就觉得php配置应该有问题: 我 yum insta ...
- Xshell配置ssh免密码登录-密钥公钥(Public key)
1 简介 ssh登录提供两种认证方式:口令(密码)认证方式和密钥认证方式.其中口令(密码)认证方式是我们最常用的一种,这里介绍密钥认证方式登录到linux/unix的方法. 使用密钥登录分为3步: 1 ...
- centos6.8 环境一键安装包 nginx配置thinkphp5
---恢复内容开始--- lnmp1.4 一键安装包 nginx配置thinkphp5 环境:Nginx1.12.1 PHP5.6 Coentos6.8 修改网站配置文件 ser ...
- Oracle HA 之ADVM和ACFS
--ADVM ADVM主要是为了使除了数据库之外的第三方应用程序也可以使用asm存储,这样不限于使asm局限于自家的数据库领域.要想使用ADVM首先必须安装grid,已经创建好了asm磁盘,asm磁盘 ...
- uchome 不从缓存中读取模板
/source/function_common.php中的代码 //模板调用 function template($name) { global $_SCONFIG, $_SGLOBAL; if($_ ...
- (转)在GitHub多个帐号上添加SSH公钥
GitHub后台可以添加多个SSH Keys,但是同一个SSH Keys只能在添加在一个帐号上(添加时提示“Key is already in use”).理由很容易想到,SSH公钥使用时相当于用户名 ...
- PAT 1073 Scientific Notation[字符串处理][科学记数法]
1073 Scientific Notation(20 分) Scientific notation is the way that scientists easily handle very lar ...
- 2.4 The Object Model -- Computed Properties and Aggregate Data with @each(计算的属性和使用@each聚合数据)
1. 通常,你可能有一个计算的属性依赖于数组中的所有元素来确定它的值.例如,你可能想要计算controller中所有todo items的数量,以此来确定完成了多少任务. export default ...
- 自定义 Repository 方法
为某一个 Repository 上添加自定义方法 步骤: 定义一个接口: 声明要添加的, 并自实现的方法 提供该接口的实现类: 类名需在要声明的 Repository 后添加 Impl, 并实现方法 ...