题目来源于leetcode Best Time to Buy and Sell Stock IV. 实质与求最大连续k个子区间和问题类似。
Say you have an array for which the i th element is the price of a given stock on day i. Design an algorithm to find the maximum profit. You may complete at most k transactions. You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
题目说是最多可交易k次,因为我们可以在同一天买进卖出多次,所以对于不足k次交易的我们总是可以扩展成交易k次,从而只需考虑正好交易k次的情况。 用动态规划做。dp[i][j]表示最后一宗交易在第j天完成,并在此期间共完成i次交易的最大利润, 关键递推式:dp[i][j] = max{ max_(0<=t<j){dp[i][t]+prices[j]-prices[t]}, max_(0<=t<=j){dp[i-1][t] + max_(t<=m<=j){prices[j]-prices[m]} } ,用到的关键子式有dp[i][t], dp[i-1][t],这些都是在dp[i][j]之前已经得到的结果。因此最直接、可读性最强的算法是
int maxProfit(int k, vector<int> &prices){
int res = 0;
int n = prices.size();
k = min(k,n-1);
//dp[i][j]为最后一宗交易在第j天完成,并在此期间共完成i次交易的最大利润
int **dp = new int*[k+1];
for(int i=0;i<=k;i++){
dp[i] = new int[n];
dp[i][0] = 0;
}
memset(dp[0],0,n*sizeof(dp[0]));
for(int i=1;i<=k;i++){
for(int j=1;j<n;j++){
//dp[i][j] = max{ max_(0<=t<j){dp[i][t]+prices[j]-prices[t]}, max_(0<=t<=j){dp[i-1][t] + max_(t<=m<=j){prices[j]-prices[m]} }
int curMax = dp[i-1][j];
int maxDif = 0; //记录max(prices[j]-prices[t])
for(int t=j-1;t>=0;t--){
curMax = max(curMax, dp[i][t]+prices[j]-prices[t]);
maxDif = max(maxDif,prices[j]-prices[t]); //更新maxDif
curMax = max(curMax, dp[i-1][t]+maxDif);
}
dp[i][j] = curMax;
res = max(res,dp[i][j]);
}
}
for(int i=0;i<=k;i++) delete dp[i];
delete []dp;
return res;
}
但是上述算法空间复杂度为O(k*n),会导致MLE。事实上我们并不需要开辟这么大的一个二维数组,只需两个一维数组就可以了,这样可以大大节省空间。
int maxProfit(int k, vector<int> &prices){
int res = 0;
int n = prices.size();
k = min(k,n-1);
//dp[i][j]为最后一宗交易在第j天完成,并在此期间共完成i次交易的最大利润
int *last = new int[n], *cur = new int[n];
for(int i=0;i<n;i++) last[i] = 0;
cur[0] = 0;
for(int i=1;i<=k;i++){
for(int j=1;j<n;j++){
//dp[i][j] = max{ max_(0<=t<j){dp[i][t]+prices[j]-prices[t]}, max_(0<=t<=j){dp[i-1][t] + max_(t<=m<=j){prices[j]-prices[m]} }
int curMax = last[j];
int maxDif = 0; //记录max(prices[j]-prices[t])
for(int t=j-1;t>=0;t--){
curMax = max(curMax, cur[t]+prices[j]-prices[t]);
maxDif = max(maxDif,prices[j]-prices[t]); //更新maxDif
curMax = max(curMax, last[t]+maxDif);
}
cur[j] = curMax;
res = max(res,cur[j]);
}
for(int j=0;j<n;j++) last[j] = cur[j];
}
delete []cur;
delete []last;
return res;
}
MLE解决了,但时间复杂度O(k * n^2),仍然TLE,仔细观察下其实可以去掉第三个循环。dp[i][j] = max(max1, max2) + prices[j],max1、max2可以看成关键递推式的前后两项,但不完全相同,这里把prices[j]剥离出来,是为了每次求dp[i][j]可以充分利用在求dp[i][j-1]时已经求好的max1、max2,以避免多余的计算。这样时间复杂度就降低一个阶,为O(k*n)。k可能会接近于甚至远大于n,可以特别考虑这种情况,以避免多余的计算,因此算法时间复杂度不会高于O(n^2)。
int maxProfit(int k, vector<int> &prices){
int res = 0;
int n = prices.size();
int mk = 0,i;
for(i=1;i<n;i++){
if(prices[i]>prices[i-1]){ res += prices[i]-prices[i-1]; mk++;}
if(mk>k){ res = 0; break; }
}
if(i>=n) return res;
//dp[i][j]为最后一宗交易在第j天完成,并在此期间共完成i次交易的最大利润
int *last = new int[n], *cur = new int[n];
for(i=0;i<n;i++) last[i] = 0;
cur[0] = 0;
for(i=1;i<=k;i++){
/* max1记录 max_(0<=t<j){dp[i][t]-prices[t]}
max2记录 max_(0<=t<=j){dp[i-1][t] - min_(t<=m<=j){prices[m]}
max1,max2与prices[j]无关。 dp[i][j] = max(max1 + prices[j], max2 + prices[j]) */
int max1 = cur[0]-prices[0], max2 = last[0]-prices[0];
int maxj = last[0];
for(int j=1;j<n;j++){
maxj = max(maxj,last[j]);
max1 = max(max1, cur[j-1]-prices[j-1]); //更新max1
max2 = max(max2, maxj-prices[j]);
cur[j] = max(max1,max2) + prices[j];
res = max(res,cur[j]);
}
for(int j=1;j<n;j++) last[j] = cur[j];
}
delete []cur;
delete []last;
return res;
}