给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路:其实对于每一天来说,只有买入(0)、卖出(1)或者不操作(2),我们定义dp[i][0]表示第i天买入后能获得的最大收益,dp[i][1]表示第i天卖出后能获得的最大收益,dp[i][2]表示第i天不操作能获得的最大收益,于是答案就是max(dp[len-1][1],dp[len-1][2])。但是,这样考虑就会发现没有办法处理这个第i天不操作的状态,因为第i天不操作的前一个状态可能是第i-1天买入、卖出或者不操作,此时并不能满足DP没有后效性的条件。所谓后效性是指,对于某个状态P,如何到达这个状态P对于P如何向下一个状态进行迁移是没有影响的。但是在上述思路中,如果是从第i-1天的买入到达第i天的不操作,那么他只能转移到第i+1天的卖出或者不操作,而如果是从第i-1天的卖出到达第i天的不操作,那么他只能转移到第i+1天的买入或者不操作,此时无法判断如何转移。
更换思路:其实经过上面分析我们发现,所谓的不操作状态等于在前面的时候进行操作,即第i天不操作的行为包含在前i-1天中的第j天进行操作,即在考虑了前j-1天的条件下,在第j天买入或者卖出,而答案即为ans=max(ans,dp[i][1])。然而,单单是这样看,我们还需要在对第i天进行决策时,对前i-1天遍历选择能使得受益最大的抉择。动态规划的含义变更为在dp[i][0]表示在第i天买入的前提下能获得的最大收益,dp[i][1]表示第i天卖出的情况下能获得的最大收益,第i天不操作的情况已经合并到在前i-1中的某一天下操作获得的最大收益。
首先写出转移方程:dp[i][0]=max(dp[j][1]-price[i]),dp[i][1]=max(dp[j][0]+price[i]);我们可以发现,price[i]是一个常数,所以方程可以化为dp[i][0]=max(dp[j][1])-price[i],dp[i][1]=max(dp[j][0])+price[i];于是很明显,我们可以设置两个变量:MaxSell表示前i-1天最后一个操作为卖出能够获得的最大收益,MinBuy表示前i-1天最后一个操作为买入所花费的最小代价,于是转移方程变为线性:dp[i][0]=MaxSell-price[i],dp[i][1]=MinBuy+price[i];MaxSell=max(MaxSell,dp[i][1]);MinBuy=min(MinBuy,dp[i][0]);
class Solution {
public:
int maxProfit(vector<int>& prices) {//dp[i] 规定必须在第i天进行操作
int len=prices.size(),i,MaxSell,MinBuy,ans=0;
if(!len)return 0;
int dp[len+10][2];// 对第i支股票的操作 0:买入 1:卖出 0—>1 1->0
MinBuy=dp[0][0]=-prices[0],MaxSell=dp[0][1]=0;
for(i=1;i<len;++i){
dp[i][0]=MaxSell-prices[i];
dp[i][1]=MinBuy+prices[i];
MaxSell=max(MaxSell,dp[i][1]);
MinBuy=max(MinBuy,dp[i][0]);
ans=max(ans,dp[i][1]);
}
return ans;
}
};
在分享一个一个有趣的思路,来自评论区的大佬:紫发的sakura
其实这题确实像一个脑筋急转弯,我们压根就不需要考虑那么多状态,直接贪心就可以,但是我们贪心的规则是,只有当能使得收益变大的时候,我们才进行累加更新,因此我们可以确保只要更新发生,收益一定会变大。具体方案是,当当前的价格大于前一天价格时候,直接累加差额。
证明:对于序列中的某一个子区间。
如果其最大值为Max,最小值为Min,且保证先取最小后取最大那么第一种方案就是取其差额作为子区间的最大收益。
第二种方案是,累加出所有的上升段,分两种情况考虑:
1.子区间本身就是递增的,那么正好结果同第一种方案
2.子区间本身不递增,但是可以将其划分为几个递增区间和游离元素,例如[5 1 4 3 6] 可以划分为 5 [1 4] [3 6] ,在满足先取最小值后取最大值的规则前提下,最大值Max肯定是属于某一个递增区间的,那么在第二种方案下,有两种情况:
A.从最小值到最大值满足递增,此时如果还剩下别的递增区间,按照第二种方案,结果一定会变大,如果没有其他递增区间,结果也等于第一种方案
B.从最小值到最大值不满足递增,假设有一个不满足点,从这个不满足点分开,即有两端和Sum1+Sum2>Sum。
举个例子,最小值为L,最大值为R的L~R的区间内有一点p不满足A[p]>A[p-1],此时将区间化为L~p-1、p~R,那么方案一的结果为A[R]-A[L],而方案二为A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p]),由于条件中已知不满足A[p]>A[p-1]即条件应为A[p]<=A[p-1],带入A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p])得到A[p-1]-A[L]+A[R]-A[p]=A[R]-A[L]+(A[p-1]-A[p])>=A[R]-A[L]。
class Solution {
public:
int maxProfit(vector<int>& prices) {//dp[i] 规定必须在第i天进行操作
int len=prices.size(),i,ans=0;
for(i=1;i<len;++i){
if(prices[i]>prices[i-1])ans+=prices[i]-prices[i-1];
}
return ans;
}
};
来源:CSDN
作者:qq_39304630
链接:https://blog.csdn.net/qq_39304630/article/details/103919101