动态规划-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] = -Infinity
T[i][0][0] = 0, T[i][0][1] = -Infinity
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的更多相关文章
- 二维剪板机下料问题(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\)号节点出发,每天等概率的走到下 ...
随机推荐
- 转载:隐式Intent
一.隐式意图介绍 显式意图我们前面已经提到,形如: Intent intent = new Intent(); intent.setClass(this,Other.class); //此句表示显式意 ...
- 后缀树 & 后缀数组
后缀树: 字符串匹配算法一般都分为两个步骤,一预处理,二匹配. KMP和AC自动机都是对模式串进行预处理,后缀树和后缀数组则是对文本串进行预处理. 后缀树的性质: 存储所有 n(n-1)/2 个后缀需 ...
- 【elasticsearch 依赖 urllib3 请问 是否 urllib3和阿里es、oss的对接出现异常】
During handling of the above exception, another exception occurred: Traceback (most recent call last ...
- 【mlflow】打包:npm run build + python setup.py sdist
mlflow是一个开源机器学习平台 最近需要使用一个它的最新版本,但是这个最新版本没有git包,无法通过pip install安装,需要打包安装. 打包完之后在项目的dist文件夹中有打包后的压缩包, ...
- 如何在python3.5环境下安装BeautifulSoup?
首先是安装: 1.到http://www.crummy.com/software/BeautifulSoup/网站上上下载 2.下载完成之后需要解压缩,假设放到D:/python下. 3.运行cmd, ...
- (2.1)学习笔记之mysql基本操作(启动与关闭)
本系列学习笔记主要讲如下几个方面: 本文主要是[一:mysql启动][二:mysql关闭] 一..mysql启动 如图,有多重启动方式 (1.1)mysql.server start :默认使用 /e ...
- 【Lua】模块与包
定义: 从用户观点来看,一个模块就是一个程序库,可以通过require来加载(require用于使用模块,module用于创建模块),然后便得到了一个全局变量,表示一个table,这个 ...
- 模块讲解----XML模块
XML实现不同语言或者程序之间进行数据交换的协议XML文件格式如下: 1.浏览器返回的字符串格式类型有: a.HTML b.Json c.XML 2.XML格式如下: <data> #表示 ...
- java 的==和equals的区别(二)
java 的==和equals的区别 java 的==和equals的区别 ==通常表明引用的是同一个东西(引用的地址相同),equals通常表明两个对象的内容相同(值相同) ------------ ...
- Linux系统——搭建FTP方式的本地定制化Yum仓库
(1)搭建公网源yum仓库 安装wget aliyun源 # wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epe ...