LeetCode | Best Time to Buy and Sell Stock III

来源:互联网 发布:淘宝卖家自动核对地址 编辑:程序博客网 时间:2024/05/01 00:12

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

Design an algorithm to find the maximum profit. You may complete at most two transactions.

Note:

You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

这次是可以进行两次股票交易,我们知道,单次最大获利可以通过DP获得,我们利用minV记录出现的交易最低价格,即可得DP方程dp[i]=max(a[i]-minV,dp[i-1])此方程的含义是最优解来自于:卖出当前股票 or 在之前卖出股票获取的最大值

于是可以得到i位置的最大获利。

但是显然还不够,我们需要找出两支股票交易获利最大值。

那么如果做过九度的合唱队形这道题就可能发现,可以从后往前找获利的最大值。

两个获利最大值相加,即为两支股票获利的最大值。


即我们需要找出两个最大差距的两个点对。

不过有人可能提出疑问,既然都是在i点那不就是从i点卖出又买入了?

那下图的情形如何解释呢?


仔细想想,其实我们的i点并不一定代表将股票卖出,而是从边界点开始往这一点所获利的最大值。

也就是说f[i]+g[i]虽然同为i,但是它们表示的意思是在i点能取得的最大利润,而非合唱队形那样以i再次为出发点找最长不下降序列。

int maxProfit(vector<int>& prices) {if(prices.size()<2) return 0;int n=prices.size();vector<int> f(n,0);vector<int> g(n,0);int small=prices[0];for(int i=1;i<n;i++){small=min(prices[i],small);f[i]=max(f[i-1],prices[i]-small);}int big=prices[n-1];for(int i=n-2;i>=0;i--){big=max(prices[i],big);g[i]=max(g[i+1],big-prices[i]);}int maxV=0;for(int i=0;i<n;i++) {if(f[i]+g[i]>maxV)maxV=f[i]+g[i]; }return maxV;}
PLUS:网上有说将其转化为差分数组再求最大m段和的情况【m=2】。

例如对于例子:1,4,6,3,7

形成差分数组[0,3,2,-3,4],于是最大2段和为3+2 + 4=9;

能够使用最大两段和的原因在于,对于同一天如果卖出和买入同时发生,那么可以当做这一天没有交易。

也就是说,,上例的最终结果应当是(6-1)+(7-3)=9;

但是,同样可以写成(6-4 + 4-1) + (7-3) = 9

对应到差分数组里,就是两项的和。

对于这几个股票题的差分数组解法,可以参考:http://www.cnblogs.com/apoptoxin/p/3770092.html

对于差分数组的了解,可以参考:https://www.zybuluo.com/rapiz/note/360515

嗯,这样就成功地转化了问题,然后最大m段和怎么求呢?

没有看过最大m子段和问题的同学可以看一下我的另一篇博客:http://blog.csdn.net/u013033845/article/details/52278766

但是~!!!

要注意状态转移有个很重要的区别在于...【这个bug修了一个多小时orz】

最大m段和要求不重叠,但是这个问题里面m段和是可以重叠的!

什么意思呢,举个例子:

2,1,4,差分数组为0,-1,3

如果按照正常的矩阵推我们可以得到:


这个结果显然是(1-2)+(4-1)=2所得到的,然而实际上有更优的策略是:

(2-2)+(1-4)=2 也就是说,在任何一天都可以当做买入卖出股票且没有收益。在考虑第2子段的时候,可以不需要为第1子段留那一个位置的空间。

相应的,dp方程变为:

dp[i][j]=max(dp[i][j-1]+a[j],max(dp[i-1][k])+a[j]);(i=<k<j)

只有这样才可以得出正确的结果。


只不过很遗憾,最大m子段和的复杂度是O(mn^2)【似乎可以优化来着】

这篇博客对这个算法进行了深入探讨:http://blog.csdn.net/liufeng_king/article/details/8632430

于是利用这个朴素思路写出来的交到oj上超时了。

int maxProfit(vector<int>& prices) {int n=prices.size();if(n<2) return 0;for(int i=n-1;i>0;i--) prices[i]=prices[i]-prices[i-1];prices[0]=0;int dp[3][n],final=0;memset(dp,0,sizeof(dp));//最大m子段和 for(int i=1;i<=2;i++){dp[i][i-1]=prices[i-1];for(int j=i;j<n-2+i;j++){int maxV=dp[i-1][i-2];for(int k=i-2;k<j;k++){if(dp[i-1][k]>maxV)maxV=dp[i-1][k];}dp[i][j]=max(dp[i][j-1],maxV)+prices[j];final=max(final,dp[i][j]);}final=max(final,dp[i][i-1]);}return final;}
于是利用了改进的方法,Accepted!!!!

改进的地方主要在于,由于每次使用到的是i行的最大值和i-1行的最大值。

故使用两个数组将其存起来,每次使用b[i]=b[i-1]+a[i] or c[i-1]+a[i]

可以确定,第i行每次进行运算之后的将是这一行当前的最大值,具体可参照下图:


故每次遍历j的时候,需要将maxV更新到b[j]

int maxProfit(vector<int>& prices) {int n=prices.size();if(n<2) return 0;//构造差分数组 for(int i=n-1;i>0;i--) prices[i]=prices[i]-prices[i-1];prices[0]=0;prices.insert(prices.begin(),0);int b[n],c[n];memset(b,0,sizeof(b));//b数组记录第i行的最大i子段和 memset(c,0,sizeof(c));//c数组记录第i-1行的最大i-1子段和 for(int i=1;i<=2;i++) {b[i]=b[i-1]+prices[i];int maxV=b[i];for(int j=i;j<=n-2+i;j++){if(b[j-1]>c[j-1])b[j]=b[j-1]+prices[j];else b[j]=c[j-1]+prices[j];c[j-1]=maxV;if(b[j]>maxV)maxV=b[j];}c[i+n-2]=maxV;}int sum = 0;      for(int j=0; j<=n; j++){          if(sum<b[j]){              sum = b[j];          }    }    return sum;}



0 0
原创粉丝点击