BestTimeSellStock I
- 問題描述:給定一組數字,代表每天股票的價格。假定現在只能進行一筆交易,計算出所能獲得利潤的最大值。
- 解法:針對每個價格,我們只要知道它前面價格的最小值即好。所有我們可以遍歷整個數組,並用一個數字代表之前的所有數字的最小值。所以針對數組裏面的每個數,我們都能獲得一個如果在該點賣出的最大利潤值。我們計算這些利潤值裏面的最大值即可。
- 時間複雜度:O(N), 空間複雜度:O(1)
- 代碼:
class Solution {
public:
int maxProfit(vector<int>& prices) {
if(prices.empty())
return 0;
int low_price = prices[0];
int profit = 0;
for(int i=1;i<prices.size();i++){
profit = max(profit, prices[i] - low_price);
if(low_price > prices[i])
low_price = prices[i];
}
return profit;
}
};
BestTimeSellStock II
- 問題描述:不同於I,你現在可以進行多筆交易。不過只能是buy-sell-buy-sell這樣交替進行,不支持buy-buy的操作。
- 分析:因爲我們可以支持多筆交易,所以針對每個價格i,如果前一個價格低於該價格,我們就可以買入&賣出。這樣就可以賺差價啦。
- 我們可能會想如果後面有更高的價格呢?比如說0, 5, 100. 我們在5的時候賺了5,但是在100的時候,我們可以賺95,所有和還是100。相當於我們在0買入,在100賣出。
- 時間複雜度:O(N), 空間複雜度:O(1)
- 代碼
int maxProfitV3(vector<int>& prices){
int res = 0;
int size = (int) prices.size();
for(int i=1;i<size;i++){
if(prices[i]>prices[i-1]){
res += (prices[i]-prices[i-1]);
}
}
return res;
}
BestTimeSellStock III
- 問題描述:不同於I和II,在本題中,我們最多隻能完成兩筆交易。不過只能是buy-sell-buy-sell這樣交替進行,不支持buy-buy的操作。
- 分析:
- 我們先回顧一下只能進行一筆交易的情況
- 我們用dp[i]表示在區間[0, i]賣出進行一次交易所能獲得的最大利潤。
- dp[i] = max(dp[i-1], prices[i] - min(prices[j])) j∈[0,i−1]
- 那現在我們可以進行兩筆交易
- 假設我們已經得到了第一筆交易的dp[0~n]
- 那麼我們如何計算第二筆交易的呢?
- 同樣我們新建一個dp2[i],表示第2次在區間[0, i]賣出進行兩次所能獲得的最大利潤。
- dp2[i] = max(dp2[i-1], prices[i] - prices[j] + dp1[j] for j in [0, i-1])
- 上面這個式子是個O(N^2)的複雜度,如果我們直接實現這個DP,會TLE,那麼我怎麼優化呢?
- dp2[i] = max(dp2[i-1], prices[i] + max(dp1[j] - prices[j]) for j in [0, i-1])
- 我們知道我們i是從0開始遍歷的,所以我們可以維護一個變量tmpMax,代表就是當前的max(dp1[j] - prices[j]) for j in [0, i-1]。在每個price計算完成後,我們再更新tmpMax = max(tmpMax, dp1[i-1] - prices[i-1]);
- 上面我們已經完成了最多兩筆交易的情況,那麼如果題目擴展一下,最多有K筆交易呢?這也就是題目BestTimeSellStock IV
- 通過上面的分析,我們已經知道最多進行兩次交易如何計算最大值了,那麼怎麼擴展到K次呢?
- 我們假設dp[k][i]表示第k次,在[0~i]內交易所能獲得的最大值。
- dp[k][i] = max{dp[k][i-1], prices[i] - prices[j] + dp[k-1][j] for j in range(0, i-1)}
- 代碼:
int maxProfitV3(vector<int>& prices){
int res = 0;
int size = (int) prices.size();
if(size == 0)
return 0;
int K = 2;
int dp[K + 1][size + 1];
memset(dp, 0, sizeof(dp));
for(int k=1;k<=K;k++){
int tmpMax = dp[k-1][0] - prices[0];
for(int i=2;i<=size;i++){
dp[k][i] = max(dp[k][i-1], prices[i-1] + tmpMax);
tmpMax = max(tmpMax, dp[k-1][i-1] - prices[i-1]);
res = max(dp[k][i], res);
}
}
return res;
}
- 分析:上面代碼的時間複雜度是O(KN), 空間複雜度是O(KN),那麼針對空間複雜度我們可以進一步優化。
- 在進行第k輪計算時,當我們計算第dp[k][i]的時候,我們只會用到前一個的dp[k-1][i-1]。
- 所以我們可以新建一個變量tmp,每次保存的就是前一輪的dp[k-1][i-1]。
- 每次更新tmp,tmp1=dp[i], update dp[i], tmp = tmp1
- 代碼:
int maxProfitV4(vector<int>& prices){
int res = 0;
int size = (int) prices.size();
if(size == 0)
return 0;
int K = 2;
int dp[size + 1];
memset(dp, 0, sizeof(dp));
for(int k=1;k<=K;k++){
int tmpMax = dp[0] - prices[0];
int saved_last = dp[1];
for(int i=2;i<=size;i++){
int tmp = dp[i];
dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
tmpMax = max(tmpMax, saved_last - prices[i-1]);
saved_last = tmp;
res = max(dp[i], res);
}
}
return dp[size];
}
- 分析:時間複雜度是O(KN), 空間複雜度我們已經降低到了O(N)。如果將上述代碼提交到IV,還是會TLE,那麼我們分析一下TLE的原因發現當我們K很大的時候,會進行很多不必要的操作。因爲我們一共就N個數字,所以最多K也應該小於等於N/2。所以當我們發現K>=N/2的時候,就表明我們可以在所有的數字上進行操作,這就轉化成了II的問題,那就是O(N)的複雜度。
- 代碼:
int maxProfitV4(int& k, vector<int>& prices){
int len = prices.size();
if (len<2) return 0;
if (k>len/2){
int ans = 0;
for (int i=1; i<len; ++i){
ans += max(prices[i] - prices[i-1],0);
}
return ans;
}
int res = 0;
int size = (int) prices.size();
if(size == 0)
return 0;
int K = k;
int dp[size + 1];
memset(dp, 0, sizeof(dp));
for(int k=1;k<=K;k++){
int tmpMax = dp[0] - prices[0];
int saved_last = dp[1];
for(int i=2;i<=size;i++){
int tmp = dp[i];
dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
tmpMax = max(tmpMax, saved_last - prices[i-1]);
saved_last = tmp;
res = max(dp[i], res);
}
}
return dp[size];
}
BestTimeSellStock with Cooldown
- 問題描述:和BestTimeSellStock II相似,沒有交易次數的限制,但是我們在sell後必須經過一天的cooldown,不能進行操作。
- 分析:
- 假設說沒有cooldown,那麼我們可以的得到dp[i] = max(dp[i-1], prices[i] - prices[j] + dp[j])。
- 但是現在我們有了一次cooldown,說明我們完成一次交易後不能立馬進行下一次交易。
- 爲了方便理解,我們更改一下上面所用的dp[i],表示在第i個節點買入所能獲得的最大利潤,那麼dp[i] = max(prices[j] - prices[i] + dp[j+2], dp[j]) for j in [i+1, n]
- 所以我們可以得到如下的代碼:
int maxProfit(vector<int>& prices) {
int size = (int) prices.size();
if(size == 0)
return 0;
int profits[size];
memset(profits, 0, sizeof(profits));
int res = 0;
for(int i=size-1;i>=0;i--){
int profit = 0;
for(int j=i+1;j<size;j++){
if((j+2) < size)
profit = max(prices[j] - prices[i] + profits[j+2], max(profit, profits[j]));
else
profit = max(prices[j] - prices[i], max(profit, profits[j]));
}
profits[i] = profit;
res = max(res, profit);
}
return res;
}
- 分析:上述代碼的時間複雜度是O(N^2),空間複雜度是O(N)。如何進行優化呢?我們發現如果沒有cooldown的時候,我們可以一直交易。但是有了cooldown了,說明我們不能每時每刻都交易了,只有在有些時候可以。那麼我們可以引出三個狀態state_reset(S0), state_buy(S1), state_sell(S2). 關係如下所示.
- 那我們可以得到他們呢之間的關係:
- S0[i] = max(S0[i-1], S2[i-1])
- S1[i] = max(S0[i-1]-prices[i], S1[i-1]) // 由S0買入
- S2[i] = S1[i-1] + prices[i]
- 所以我們可以得到一個O(N)的代碼:
int maxProfitV2(vector<int>& prices){
int size = (int) prices.size();
if(size == 0)
return 0;
int state_reset[size];
int state_buy[size];
int state_sell[size];
memset(state_reset, 0, sizeof(state_reset));
memset(state_buy, 0, sizeof(state_buy));
memset(state_sell, 0, sizeof(state_sell));
state_reset[0] = 0;
state_buy[0] = -prices[0];
state_sell[0] = -0x7FFFFFFF;
for(int i=1;i<size;i++){
state_reset[i] = max(state_reset[i-1], state_sell[i-1]);
state_buy[i] = max(state_reset[i-1] - prices[i], state_buy[i-1]);
state_sell[i] = state_buy[i-1] + prices[i];
}
return max(state_sell[size-1], max(state_reset[size-1], state_buy[size-1]));
}