天天看點

LeetCode | Best Time to Buy and Sell Stock III

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位置的最大獲利。

但是顯然還不夠,我們需要找出兩支股票交易獲利最大值。

那麼如果做過九度的合唱隊形這道題就可能發現,可以從後往前找獲利的最大值。

兩個獲利最大值相加,即為兩支股票獲利的最大值。

LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III

即我們需要找出兩個最大差距的兩個點對。

不過有人可能提出疑問,既然都是在i點那不就是從i點賣出又買入了?

那下圖的情形如何解釋呢?

LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III

仔細想想,其實我們的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

如果按照正常的矩陣推我們可以得到:

LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III

這個結果顯然是(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行每次進行運算之後的将是這一行目前的最大值,具體可參照下圖:

LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III
LeetCode | Best Time to Buy and Sell Stock III

故每次周遊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;
}