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
]
j\in[0, i-1]
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]));
}