题目:https://oj.leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/
题意:已知第1天、第2天......第N天的股票价格,每天只能进行一次买或者卖,且规定手里的股票卖出之前不能买进新的股票,问在最多进行K次交易的情况下,采用最优方案净利润能有多少。
分析:
1、设j天完成不超过i次交易能得到的最大收益是f(i,j),显然f(0,0) = 0,f(i, 1) = 0(因为只有1天不能完成任何交易)
2、对最后一天也就是第j天发生的情况进行分类,第j天可能没有买卖,也可能最后一次卖出手里的股票,即
f(i, j) = max{ f(i-1, j), f(i, j-1), f(i-1,k-1) + a[j] - a[k] },其中k >= 1 && k < j,即最后一天卖出的股票可能是第1天、第2天......第j-1天买入的股票
3、状态方程有了,似乎可以直接写了,但由于k的存在,这一算法的复杂度是O(k*n*n)的,在大数据情况下会TLE,我们看看能不能对后面这个f(i-1,k-1) + a[j] - a[k]做一些分析
4、我们令g(i, j) = f(i-1, k-1) - a[k],其中k >= 1 && k < j,那它表示什么意思呢,即最后第j天之前,最多进行了i-1次交易,且最后一次买入发生在第k天,这一过程之后手里money最多还有多少(或最少还欠多少),显然g(i, 1) = 0(因为第1天之前最多进行0次交易),g(i, 2) = f(i-1, 0) - a[1],将k带入,我们可以看到:
g(i, j) = max{ f(i-1, 0) - a[1], f(i-1, 1) - a[2], ..., f(i-1, j-2) - a[j-1] }
g(i, j+1) = max{ f(i-1, 0) - a[1], f(i-1, 1) - a[2], ..., f(i-1, j-2) - a[j-1], f(i-1, j-1) - a[j] }
= max{ g(i, j), f(i-1, j-1) - a[j] }
即g(i, j)的递推会用到f(i, j)的状态
5、将g(i, j)带入我们的状态方程,则
g(i, j) = max{ g(i, j-1), f(i-1, j-2) - a[j-1] }
f(i, j) = max{ f(i-1, j), f(i, j-1), g(i, j) + a[j] }
可以看到g的当前状态仅和本层前一个状态、f的上一层前一个状态有关,f当前状态仅和上一层的当前状态、本层的前一个状态、g的当前状态有关,因此我们可以采用滚动数组的形式,仅保留2层状态,即能完成状态转移的过程
6、最后还有一个优化(有点像多重背包可以分成完全背包和0-1背包):
当2k >= n时,即我们可以做到在任意一天买入任意之后一天卖出,在直方图中容易看出这相当于只要有利可图我们就能获得
class Solution {
public:
int allProfit(vector<int>& prices){
int tot = 0;
for(int i = 1; i < prices.size(); ++i){
tot += max(0, prices[i] - prices[i-1]);
}
return tot;
}
int maxProfit(int k, vector<int> &prices) {
int n = prices.size();
if(k * 2 >= n) return allProfit(prices);
vector<int> mem((n + 1) * 2, 0);
int *pref = &mem[0], *curf = &mem[n+1], g, *a = &prices[0] - 1;
for(int i = 1; i <= k; ++i){
//递推第1天和第2天的情况,因为比较特殊
curf[1] = 0; //g[1] = 0, g[2] = pref[0] - a[1]
curf[2] = max(0, a[2] - a[1]); g = -a[1];
//递推之后的情况
for(int j = 3; j <= n; ++j){
g = max(g, pref[j-2] - a[j-1]);
curf[j] = max(max(pref[j], curf[j-1]), g + a[j]);
}
swap(pref, curf);
}
return pref[n];
}
};